Reading a shader from a .txt file using a structure

4.1k Views Asked by At

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.

3

There are 3 best solutions below

4
On BEST ANSWER

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.

5
On

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

0
On

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);
}