void replaceIncludes(string &src, string &dest, const string &directive, string &alreadyIncluded, bool onlyOnce) { int start = 0; while (true) { int includeIndex = (int)src.find("#include", start); if (includeIndex < 0) { dest += src.substr(start); break; } if (includeIndex > 0 && !isspace(src[includeIndex - 1])) continue; dest += src.substr(start, includeIndex - 1); // int quoteStart = (int)src.find("\"", start + 8); int quoteEnd = (int)src.find("\"", quoteStart + 1); start = quoteEnd + 1; if (quoteStart >= quoteEnd) { ERROR_MSG("could not replace includes"); break; } string includeFileName = src.substr(quoteStart + 1, (quoteEnd - quoteStart - 1)); if ((int)alreadyIncluded.find(includeFileName) < 0) { if (onlyOnce) { alreadyIncluded.append("|"); alreadyIncluded.append(includeFileName); } string subSource; loadFileAsString(includeFileName, subSource); replaceIncludes(subSource, dest, directive, alreadyIncluded, onlyOnce); } } }
GLuint loadShader(const string &fileName, GLuint shaderType) { // load the shader as a file string mainCode; if (!loadFileAsString(fileName, mainCode)) { ERROR_MSG("Could not load file '" + fileName + "'", false); return NULL_HANDLE; } string shaderCode; string alreadyIncluded = fileName; replaceIncludes(mainCode, shaderCode, "#include", alreadyIncluded, true); // print the shader code #ifdef _DEBUG //cout << "\n----------------------------------------------- SHADER CODE:\n"; //cout << shaderCode << endl; //cout << "--------------------------------------------------------------\n"; #endif // transfer shader code to card and compile GLuint shaderHandle = glCreateShader(shaderType); // create handle for the shader const char* source = shaderCode.c_str(); // get C style string for shader code glShaderSource(shaderHandle, 1, &source, NULL); // pass the shader code to the card glCompileShader(shaderHandle); // attempt to compile the shader // check to see if compilation worked // If the compilation did not work, print an error message and return NULL handle int status; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { ERROR_MSG("compiling shader '" + fileName + "'", false); GLint msgLength = 0; glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &msgLength); std::vector<char> msg(msgLength); glGetShaderInfoLog(shaderHandle, msgLength, &msgLength, &msg[0]); printf("%s\n", &msg[0]); glDeleteShader(shaderHandle); exit(0); return NULL_HANDLE; } return shaderHandle; }
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 separated 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"; } 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) { length = 4096; } 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 ? vshSource : 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"; } 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) { length = 4096; } 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 ? fshSource : 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) { length = 4096; } 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 therefore 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 (length > 3) { // If this is an array uniform, strip 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; if (uniformType == GL_SAMPLER_2D) { uniform->_index = samplerIndex; samplerIndex += uniformSize; } else { uniform->_index = 0; } effect->_uniforms[uniformName] = uniform; } SAFE_DELETE_ARRAY(uniformName); } } return effect; }
static 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 fileLen = str.length(); size_t 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); } } }