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