问题
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.
回答1:
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:
source = allLines.c_str();
where source
is a class/struct member. The c_str()
method on the string
gives you a pointer to the internal string data of the object. As explained above, this data is freed when allLines
goes out of scope at the end of the shaderReader
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:
struct shaderReader
{
shaderReader::shaderReader(std::string);
std::string source;
};
Then in the constructor, you can read the source code directly into this class member:
while (std::getline(theFile, line))
{
source = source + line + "\n";
}
The only reason why you have to bother with a char*
is because glShaderSource()
needs one. The safest approach is to convert this at the last moment:
const GLchar* ob1 = vertexShader.source.c_str();
GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObject, 1, &ob1, NULL);
glCompileShader(vertexShaderObject);
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.
回答2:
change
source = allLines.c_str();
into
source = new char[allLines.length()+1];
strcpy(source,allLines.c_str());
And in the destructor of shaderReader, release the memory allocated for source
if(source)
{
delete[] source;
source = nullptr;
}
Ref: https://stackoverflow.com/a/12862777/3427520
回答3:
I would be tempted to store your shader in a std::vector<GLchar>
so you don't need to worry about allocating and deallocating memory:
Maybe something like this:
struct shaderReader
{
shaderReader(std::string);
std::vector<GLchar> source;
const GLchar* data;
GLint size;
};
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.assign(allLines.begin(), allLines.end());
size = source.size();
data = source.data();
theFile.close();
}
else
{
std::cout << "Unable to open file.";
}
}
int main()
{
shaderReader sr("fragment-shader.txt");
GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(shader, 1, &sr.data, &sr.size);
}
来源:https://stackoverflow.com/questions/30794981/reading-a-shader-from-a-txt-file-using-a-structure