I've started learning how to use OpenGL and I absolutely hate having my shaders declared as global variables before my main().
I thought it would be cool to make a structure or class that would read the shader from a file in my project directory. The file reading works perfectly fine, but for some reason it won't actually output an image like it would if I had the shader declared before main. Here is my example.
Shader Reading Structure:
#include "allHeader.h"
struct shaderReader
{
shaderReader::shaderReader(std::string);
const GLchar* source;
};
shaderReader::shaderReader(std::string name)
{
std::string line, allLines;
std::ifstream theFile(name);
if (theFile.is_open())
{
while (std::getline(theFile, line))
{
allLines = allLines + line + "\n";
}
source = allLines.c_str();
std::cout << source;
theFile.close();
}
else
{
std::cout << "Unable to open file.";
}
}
Snapshot of Area right before main()
shaderReader vertexShader = shaderReader("vertex.txt");
shaderReader fragmentShader = shaderReader("fragment.txt");
const GLchar* vertexSource =
"#version 150 core\n"
"in vec2 position;"
"void main() {"
" gl_Position = vec4(position, 0.0, 1.0);"
"}";
const GLchar* fragmentSource =
"#version 150 core\n"
"out vec4 outColor;"
"void main() {"
" outColor = vec4(1.0, 1.0, 1.0, 1.0);"
"}";
int main()
{
//stuff
}
Works
GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObject, 1, &vertexSource, NULL);
glCompileShader(vertexShaderObject);
//Now let's create the Fragment Shader Object
GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderObject, 1, &fragmentSource, NULL);
glCompileShader(fragmentShaderObject);
Doesn't work for some Reason
const GLchar* ob1 = vertexShader.source;
const GLchar* ob2 = fragmentShader.source;
GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObject, 1, &ob1, NULL);
glCompileShader(vertexShaderObject);
//Now let's create the Fragment Shader Object
GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderObject, 1, &ob2, NULL);
glCompileShader(fragmentShaderObject);
Also Doesn't Work
GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObject, 1, &vertexShader.source, NULL);
glCompileShader(vertexShaderObject);
//Now let's create the Fragment Shader Object
GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderObject, 1, &fragmentShader.source, NULL);
glCompileShader(fragmentShaderObject);
The above code when working prints out a white triangle in the middle of a black screen. However, when not working I just get a black screen. I tried checking the shaders for compile errors and I got no errors at all. I'm think perhaps something is wrong within the structure, something there I didn't do right. Thank you for reading my question, if you had a similar problem I hope my question helps you.
You're using a mix of C++ (
std::string
) and C (char*
) strings in an invalid way.In the constructor, you're building up the code in a C++
string
, which is an object that automatically allocates and re-allocates the memory to hold the string data as the string grows. It will also automatically free that data when it goes out of scope at the end of the constructor.The root of the problem is here:
where
source
is a class/struct member. Thec_str()
method on thestring
gives you a pointer to the internal string data of the object. As explained above, this data is freed whenallLines
goes out of scope at the end of theshaderReader
constructor, so you end up with a pointer to freed memory. This results in undefined behavior, because this memory could now be used for something else.The cleanest way to fix this is to use C++ strings consistently. Change the struct definition to:
Then in the constructor, you can read the source code directly into this class member:
The only reason why you have to bother with a
char*
is becauseglShaderSource()
needs one. The safest approach is to convert this at the last moment:This way, you only use the pointer to the internal
string
data very temporarily, and you don't have to bother with C-style strings and memory management otherwise.