Question

FIXED

Everything works now! Scroll down to see the sollution.

The perimeter

I am currently writing an OpenGL Engine. I wrote a wrapper for OpenGL Shaders:

public abstract class Shader {
    protected String name;
    protected int type;
    protected String code;
    protected int shader;
    protected boolean loaded;

    public Shader(String name, String code) {
        this.name = name;
        this.code = code;
        this.loaded = false;
        this.type = -1;
    }

    public Shader(Shader s) {
        this.name = s.name;
        this.type = s.type;
        this.code = s.code;
        this.shader = -1;
        this.loaded = false;
    }

    public String getName() {
        return this.name;
    }

    public int getType() {
        return this.type;
    }

    public boolean isVertexShader() {
        return this.type == GLES20.GL_VERTEX_SHADER;
    }

    public boolean isFragmentShader() {
        return this.type == GLES20.GL_FRAGMENT_SHADER;
    }

    public void unload() {
        this.loaded = false;
    }

    public int load() {
        if(!this.loaded) {
            this.shader = GLES20.glCreateShader(this.type);

            GLES20.glShaderSource(this.shader, this.code);
            GLES20.glCompileShader(this.shader);
            DrainEmEngine.checkGlError("glCompileShader");

            this.loaded = true;
        }

        return this.shader;
    }
}

And I have a wrapper for programs:

public class Program {
    private String name;
    private VertexShader vertexShader;      // VertexShader and FragmentShader just 
    private FragmentShader fragmentShader;  // extend the Shader class and set it's type
    private int program;
    private boolean loaded;

    public Program(String name, VertexShader vertexShader, FragmentShader fragmentShader) {
        this.name = name;
        this.vertexShader = vertexShader;
        this.fragmentShader = fragmentShader;
        this.program = -1;
        this.loaded = false;
    }

    public Program(Program p) {
        this.name = p.name;
        this.vertexShader = new VertexShader(p.vertexShader);
        this.fragmentShader = new FragmentShader(p.fragmentShader);
        this.program = -1;
        this.loaded = false;
    }

    public String getName() {
        return this.name;
    }
    public VertexShader getVertexShader() {
        return this.vertexShader;
    }

    public FragmentShader getFragmentShader() {
        return this.fragmentShader;
    }

    public void unload() {
        this.vertexShader.unload();
        this.fragmentShader.unload();
        this.loaded = false;
    }

    public int load() {
        if(!this.loaded) {
            this.program = GLES20.glCreateProgram();
            DrainEmEngine.checkGlError("glCreateProgram");
            GLES20.glAttachShader(this.program, this.vertexShader.load());
            DrainEmEngine.checkGlError("glAttachShader"); // this gives Error 1281
            GLES20.glAttachShader(this.program, this.fragmentShader.load());
            DrainEmEngine.checkGlError("glAttachShader");
            GLES20.glLinkProgram(this.program);
            DrainEmEngine.checkGlError("glLinkProgram");
        }

        return this.program;
    }
}

I know this is far from complete, but I only just started with this project...

My engine is supposed to come with some predefined shaders and a 3d object can get a shader or program from the Engine.

// In constructor of the 3d object
this.program = DrainEmEngine.getInstance().getProgram("default");
// I also tried this, but same effect: 
this.program = new Program(DrainEmEngine.getInstance().getProgram("default"));

The default program is initialized like this:

    public void registerDefaultShaders() {
        this.registerShader(new VertexShader("default_ver", 
                "uniform mat4 uMVPMatrix;" +
                "attribute vec4 vPosition;" +
                "void main() {" +
                "  gl_Position = uMVPMatrix * vPosition;" +
                "}"));
        this.registerShader(new FragmentShader("default_frag", 
                "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                "  gl_FragColor = vColor;" +
                "}"));
    }

    public void registerDefaultPrograms() {
        this.registerProgram(new Program("default", (VertexShader)this.getShader("default_ver"), (FragmentShader)this.getShader("default_frag")));
    }

This is my render/draw method:

public void render(float[] mvpMatrix) {
        // Add program to OpenGL ES environment
        // this.program.unload(); // If this line is commented out it crashes!
        int program = this.program.load();
        GLES20.glUseProgram(program);
        DrainEmEngine.checkGlError("glUseProgram");

        // get handle to vertex shader's vPosition member
        int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
        DrainEmEngine.checkGlError("glGetAttribLocation");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(positionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(positionHandle, Mesh.COORDS_PER_VERTEX,
                                     GLES20.GL_FLOAT, false,
                                     Mesh.vertexStride, this.mesh.getVertexBuffer());

        // get handle to fragment shader's vColor member
        int colorHandle = GLES20.glGetUniformLocation(program, "vColor");
        DrainEmEngine.checkGlError("glGetUniformLocation");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(colorHandle, 1, color, 0);
        DrainEmEngine.checkGlError("glUniform4fv");

        // get handle to shape's transformation matrix
        int MVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
        DrainEmEngine.checkGlError("glGetUniformLocation");

        updateModelMatrix();
        float[] tmp = mvpMatrix.clone();
        Matrix.multiplyMM(tmp, 0, mvpMatrix, 0, modelMatrix, 0);

        // Apply the projection and view transformation
        GLES20.glUniformMatrix4fv(MVPMatrixHandle, 1, false, tmp, 0);
        DrainEmEngine.checkGlError("glUniformMatrix4fv");

        // Draw 
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, this.mesh.getDrawOrder().length,
                GLES20.GL_UNSIGNED_SHORT, this.mesh.getDrawListBuffer());

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(positionHandle);
    }

I load the programs and the shaders at startup and after that they should work fine.

The problem

However if I comment out the marked line (which it should be I guess) everything crashes and I get the OpenGL Error 1281 in the Program.load() function after calling glAttachShader.
But since the Shader is getting compiled successfully (if calling DrainEmEngine.checkGlError("...") is enough to verify that) at least once, it should be working...

If I force the shaders and programs to recompile in each frame (by uncommenting the line) it works just fine but from all I know about OpenGL that is NOT the way to go.

Since I am pretty new to OpenGL and all my research didn't help me fix the problem I am hoping that one of you spots my error or is able to enlighten me in the ways of OpenGL and can tell me where my logic is failing.

The solution

I don't know what the problem was but thanks to the post of ExMix I solved it.

Here is what my Shader and Program classes look like now:
(The actual fix is in the load and unload methods)

public abstract class Shader {
    protected String name;
    protected int type;
    protected String code;
    protected int shader;
    protected boolean loaded;

    public Shader(String name, String code) {
        this.name = name;
        this.code = code;
        this.loaded = false;
        this.type = -1;
    }

    public Shader(Shader s) {
        this.name = s.name;
        this.type = s.type;
        this.code = s.code;
        this.shader = -1;
        this.loaded = false;
    }

    public String getName() {
        return this.name;
    }

    public int getType() {
        return this.type;
    }

    public boolean isVertexShader() {
        return this.type == GLES20.GL_VERTEX_SHADER;
    }

    public boolean isFragmentShader() {
        return this.type == GLES20.GL_FRAGMENT_SHADER;
    }

    public void unload() {
        if(this.loaded) {
            GLES20.glDeleteShader(this.shader);
            DrainEmEngine.checkGlError("glDeleteShader");
            this.loaded = false;
        }
    }

    public int load() {
        if(!this.loaded) {
            this.shader = GLES20.glCreateShader(this.type);

            GLES20.glShaderSource(this.shader, this.code);
            GLES20.glCompileShader(this.shader);
            DrainEmEngine.checkGlError("glCompileShader");

            this.loaded = true;
        }

        return this.shader;
    }
}

public class Program {
    private String name;
    private VertexShader vertexShader;
    private FragmentShader fragmentShader;
    private int program;
    private boolean loaded;

    public Program(String name, VertexShader vertexShader, FragmentShader fragmentShader) {
        this.name = name;
        this.vertexShader = vertexShader;
        this.fragmentShader = fragmentShader;
        this.program = -1;
        this.loaded = false;
    }

    public Program(Program p) {
        this.name = p.name;
        this.vertexShader = new VertexShader(p.vertexShader);
        this.fragmentShader = new FragmentShader(p.fragmentShader);
        this.program = -1;
        this.loaded = false;
    }

    public String getName() {
        return this.name;
    }
    public VertexShader getVertexShader() {
        return this.vertexShader;
    }

    public FragmentShader getFragmentShader() {
        return this.fragmentShader;
    }

    public void unload() {
        this.vertexShader.unload();
        this.fragmentShader.unload();
        this.loaded = false;
    }

    public int load() {
        if(!this.loaded) {
            this.program = GLES20.glCreateProgram();
            DrainEmEngine.checkGlError("glCreateProgram");
            int vert = this.vertexShader.load();
            GLES20.glAttachShader(this.program, vert);
            DrainEmEngine.checkGlError("glAttachShader");
            int frag = this.fragmentShader.load();
            GLES20.glAttachShader(this.program, frag);
            DrainEmEngine.checkGlError("glAttachShader");
            GLES20.glLinkProgram(this.program);
            DrainEmEngine.checkGlError("glLinkProgram");
            GLES20.glDetachShader(this.program, vert);
            DrainEmEngine.checkGlError("glDetachShader");
            GLES20.glDetachShader(this.program, frag);
            DrainEmEngine.checkGlError("glDetachShader");
            this.vertexShader.unload();
            this.fragmentShader.unload();
        }

        return this.program;
    }
}
Was it helpful?

Solution

At first your code actualy not unload shaders and programs. You have leak of OpenGL resources.

for unload you must do: 1) glDetachShader(programID, shaderID) for vertex and fragment shader 2) glDeleteShader(shaderID) for vertex and fragment shader 3) glDeleteProgram(programID)

after this your shader will be fully unloaded.

But if you uncomment "unload" line code will be create new gpu shader objects and gpu program objects (not your wrapper) on each frame.

Also, after program been linked you can detach shaders and delete it's. This process is like C++ compilation. Program link from *.obj files, but after progran been linked *.obj files does't need.

About crash. Are you sure that it crash on first frame? Can you insert some static counter, that will be count frames before crash? It can be crash because OpenGL in some moment can't allocate names for shaders and programs.

Also you need to check checkGlError function.

http://www.khronos.org/opengles/sdk/docs/man/

To allow for distributed implementations, there may be several error flags. If any single error flag has recorded an error, the value of that flag is returned and that flag is reset to GL_NO_ERROR when glGetError is called. If more than one flag has recorded an error, glGetError returns and clears an arbitrary error flag value. Thus, glGetError should always be called in a loop, until it returns GL_NO_ERROR, if all error flags are to be reset.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top