I have two different programs, and four different shaders:
3d program -> 3d vertex shader, 3d fragment shader
2d program -> 2d vertex shader, 2d fragment shader
I render using the 3d program first, 2d second. I’m using 4 textures for the 3D scene and 5 for the 2D overlay (3 of the textures are shared). This results in the last texture from the 2d overlay to use the texture at index zero instead. However, if I have the same amount of textures (4 & 4, 3 & 3, etc…) for both passes then the problem looks like it goes away (get the expected results).
Here’s the sampler usage in the 2d & 3d programs:
uniform sampler2D diffuse_maps[16];
color += texture(diffuse_maps[int(tex_index)], tex_coord);
}
Note: not sure if it’s relevant, but if I add another uniform to the 3d vertex shader openGL throws a 1282: invalid operation error when I try to bind my textures:
private void bindTextures(GL3 gl)
{
for(int i = 0; i < m_textures.size(); i++)
{
m_shader.bindTexture(gl, i, m_textures.get(i).m_id);
m_shader.setUniform1i(gl, "diffuse_maps["+i+"]", i);
}
}
bindTexture and setUniform
public void bindTexture(GL3 gl, int unit, int id)
{
gl.glActiveTexture(GL3.GL_TEXTURE0 + unit);
//ERROR!!!!!!!!!!!!!!!!!!!! occurs on the line below
gl.glBindTexture(GL3.GL_TEXTURE_2D, id);//<---ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
public void setUniform1i(GL3 gl, String name, int x)
{
gl.glUniform1i(wrapLocation(m_uniforms, name), x);
}
private Integer wrapLocation(HashMap<String, Integer> map, String name)
{
Integer location = map.get(name);
if(location == null)
{
Logger.log(WARNING, "Could not bind shader var: " + name, getName());
return -1;
}
return location;
}
I don’t know what you mean, do you want to see the entire shader class? Maybe I should’ve been more clear, I have reformatted the first post, the methods called (bindTexture and setUniform) are already there.
The textures are generally numbered 1, 6, 7, 8, 9 in openGL. They are bound using glActiveTexture() between 0-4.
Please have a look at my first post again, I have added more comments so you can see exactly which line the error occurs on. (Note that the error is only thrown if the number of textures being used by both renderers differs in number (quantity), doesn’t matter which textures are used or if I share textures between them).
Anyway here’s more code:
package Roasted.Graphics.Renderer;
import static Roasted.Utilities.Logger.LogLevel.DEBUG;
import static Roasted.Utilities.Logger.LogLevel.ERROR;
import static Roasted.Utilities.Logger.LogLevel.WARNING;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import javax.media.opengl.GL3;
import Roasted.Graphics.IGraphicGL;
import Roasted.Utilities.EXIT_CODE;
import Roasted.Utilities.Logger;
import Roasted.Utilities.SStringOperations;
public class Shader implements IGraphicGL
{
private enum LOC_TYPE
{
ATTRIB,
UNIFORM,
UNIFORM_BUFFER;
};
private class ShaderMethod
{
@SuppressWarnings("rawtypes")
private Class[] m_classes;
private Object[] m_params;
private String m_method_name;
public ShaderMethod(String method_name, Object[] params, @SuppressWarnings("rawtypes") Class[] classes)
{
m_method_name = method_name;
m_params = params;
m_classes = classes;
}
@SuppressWarnings("rawtypes")
public Class[] getClasses()
{
return m_classes;
}
public String getMethodName()
{
return m_method_name;
}
public Object[] getParams()
{
return m_params;
}
}
public static final int MAX_TEXTURES = 16;
private static String sm_path = "res/shaders/";
private boolean m_valid;
private int m_handle;
private String m_name;
private String m_vert_shader_path;
private String m_frag_shader_path;
private Thread m_thread;
private ArrayList<ShaderMethod> m_methods;
private HashMap<String, Integer> m_attribs;
private HashMap<String, Integer> m_uniforms;
private HashMap<String, Integer> m_uniform_blocks;
public Shader(String vert_shader_path, String frag_shader_path)
{
m_name = vert_shader_path.substring(0, vert_shader_path.indexOf("."));
m_vert_shader_path = vert_shader_path;
m_frag_shader_path = frag_shader_path;
m_uniforms = new HashMap<String, Integer>();
m_attribs = new HashMap<String, Integer>();
m_uniform_blocks = new HashMap<String, Integer>();
m_methods = new ArrayList<ShaderMethod>();
}
private void bindAttributes(GL3 gl)
{
bindLocation(gl, LOC_TYPE.ATTRIB, m_attribs, "vert_pos");
bindLocation(gl, LOC_TYPE.ATTRIB, m_attribs, "tex_coord");
bindLocation(gl, LOC_TYPE.ATTRIB, m_attribs, "tex_index");
bindLocation(gl, LOC_TYPE.ATTRIB, m_attribs, "color");
bindLocation(gl, LOC_TYPE.ATTRIB, m_attribs, "matrix_index");
}
private void bindLocation(GL3 gl, LOC_TYPE type, HashMap<String, Integer> map, String name)
{
int location = -1;
switch(type)
{
case ATTRIB:
location = gl.glGetAttribLocation(m_handle, name);
break;
case UNIFORM:
location = gl.glGetUniformLocation(m_handle, name);
break;
case UNIFORM_BUFFER:
location = gl.glGetUniformBlockIndex(m_handle, name);
break;
default:
Logger.log(ERROR, "LOC_TYPE doesn't exist, could not bind to " + name, getName());
System.exit(EXIT_CODE.SHADER_FAILURE.err_code);
break;
}
map.put(name, location);
}
public void bindTexture(GL3 gl, int unit, int id)
{
gl.glActiveTexture(GL3.GL_TEXTURE0 + unit);
gl.glBindTexture(GL3.GL_TEXTURE_2D, id);
}
private void bindUniformBufferObjects(GL3 gl)
{
bindLocation(gl, LOC_TYPE.UNIFORM_BUFFER, m_uniform_blocks, "matrixbuffers");
}
private void bindUniforms(GL3 gl)
{
bindLocation(gl, LOC_TYPE.UNIFORM, m_uniforms, "model_matrix");
bindLocation(gl, LOC_TYPE.UNIFORM, m_uniforms, "view_matrix");
bindLocation(gl, LOC_TYPE.UNIFORM, m_uniforms, "projection_matrix");
bindLocation(gl, LOC_TYPE.UNIFORM, m_uniforms, "light_pos");
for(int i = 0; i < MAX_TEXTURES; i++)
bindLocation(gl, LOC_TYPE.UNIFORM, m_uniforms, "diffuse_maps["+i+"]");
}
private int buildShader(GL3 gl, int shader_type, String shader_location)
{
File shader_file = new File(sm_path + shader_location);
int shader_handle = gl.glCreateShader(shader_type);//grab a handle
//Find the vert file to load
ArrayList<String> src_array = new ArrayList<String>();
Scanner file_scanner = null;
try { file_scanner = new Scanner(shader_file); }
catch (FileNotFoundException e) { Logger.log(ERROR, m_name + " not found!", getName()); }
//Put the code into an array
file_scanner.useDelimiter("\n");//Use the return char during scanning
while(file_scanner.hasNext())
{
String temp = file_scanner.next() + "\n";
src_array.add(temp);
}
file_scanner.close();
//Convert the array over to a String array so we can compile it
String[] shader_source = new String[src_array.size()];
shader_source = src_array.toArray(shader_source);
int [] lengths= new int[shader_source.length];
for(int i = 0; i < shader_source.length; i++)
{
lengths[i] = shader_source[i].length();
}
//Compile the shader
gl.glShaderSource(shader_handle, lengths.length, shader_source, lengths, 0);
gl.glCompileShader(shader_handle);
checkForShaderErrors(gl, shader_handle, shader_type, shader_location);
gl.glAttachShader(m_handle, shader_handle);
return shader_handle;
}
private void checkForProgramErrors(GL3 gl, int status_type)
{
int[] result = new int[1];
gl.glGetProgramiv(m_handle, status_type, result, 0);
if(result[0] == GL3.GL_FALSE)
{
if(status_type == GL3.GL_LINK_STATUS)
Logger.log(ERROR, "Failed to link", getName());
if(status_type == GL3.GL_VALIDATE_STATUS)
Logger.log(ERROR, "Failed to validate", getName());
int[] info_log_length = new int[1];
gl.glGetProgramiv(m_handle, GL3.GL_INFO_LOG_LENGTH, info_log_length, 0);
byte[] info_log = new byte[info_log_length[0]];
gl.glGetProgramInfoLog(m_handle, info_log.length, info_log_length, 0, info_log, 0);
// for(int i = 0; i < info_log.length; i++)
// System.err.print((char)info_log[i]);
Logger.log(DEBUG, SStringOperations.byteArrayToString(info_log), getName());
if(status_type == GL3.GL_LINK_STATUS)
{
Logger.log(ERROR, "Unable to continue, exiting!", getName());
System.exit(EXIT_CODE.SHADER_FAILURE.err_code);
}
}
}
private void checkForShaderErrors(GL3 gl, int shader_handle, int shader_type, String shader_location)
{
int[] result = new int[1];
gl.glGetShaderiv(shader_handle, GL3.GL_COMPILE_STATUS, result, 0);
if(result[0] == GL3.GL_FALSE)
{
Logger.log(ERROR, "Failed to compile", getName());
int[] info_log_length = new int[1];
gl.glGetShaderiv(shader_handle, GL3.GL_INFO_LOG_LENGTH, info_log_length, 0);
byte[] info_log = new byte[info_log_length[0]];
gl.glGetShaderInfoLog(shader_handle, info_log.length, info_log_length, 0, info_log, 0);
// for(int i = 0; i < info_log.length; i++)
// System.err.print((char)info_log[i]);
Logger.log(ERROR, SStringOperations.byteArrayToString(info_log), getName());
Logger.log(ERROR, "Unable to continue, exiting!", getName());
System.exit(EXIT_CODE.SHADER_FAILURE.err_code);
}
}
@Override
public void cleanup(GL3 gl)
{
gl.glDeleteShader(m_handle);
}
public void disable(GL3 gl)
{
gl.glUseProgram(0);
}
public void enable(GL3 gl)
{
gl.glUseProgram(m_handle);
}
public int getAttribLocation(String name)
{
return wrapLocation(m_attribs, name);
}
public String getName()
{
return m_name;
}
@Override
public Thread getThread()
{
return m_thread;
}
public int getUniformLocation(String name)
{
return wrapLocation(m_uniforms, name);
}
public int getUniformBlockLocation(String name)
{
return wrapLocation(m_uniform_blocks, name);
}
@Override
public void init(GL3 gl)
{
m_handle = gl.glCreateProgram();
int v = buildShader(gl, GL3.GL_VERTEX_SHADER, m_vert_shader_path);
int f = buildShader(gl, GL3.GL_FRAGMENT_SHADER, m_frag_shader_path);
linkProgram(gl, v, f);
//Bind the attributes before we link
bindAttributes(gl);
//Bind uniforms after link
bindUniforms(gl);
//Bind UBOs
bindUniformBufferObjects(gl);
}
private void linkProgram(GL3 gl, int v, int f)
{
gl.glLinkProgram(m_handle);
checkForProgramErrors(gl, GL3.GL_LINK_STATUS);
gl.glValidateProgram(m_handle);
checkForProgramErrors(gl, GL3.GL_VALIDATE_STATUS);
m_valid = gl.glIsProgram(m_handle);//Determine if the shader is valid for our uses.
//So, I guess that you can and should do this?
gl.glDetachShader(m_handle, v);
gl.glDetachShader(m_handle, f);
}
public void setPath(String path)
{
sm_path = path;
}
@Override
public void setThread(Thread thread)
{
m_thread = thread;
}
public void setUniform1f(GL3 gl, String name, float x)
{
gl.glUniform1f(wrapLocation(m_uniforms, name), x);
}
public void setUniform1fv(GL3 gl, String name, int count, float[] value)
{
gl.glUniform1fv(wrapLocation(m_uniforms, name), count, value, 0);
}
public void setUniform1i(GL3 gl, String name, int x)
{
gl.glUniform1i(wrapLocation(m_uniforms, name), x);
}
public void setUniform1iv(GL3 gl, String name, int count, int[] value)
{
gl.glUniform1iv(wrapLocation(m_uniforms, name), count, value, 0);
}
public void setUniform2f(GL3 gl, String name, float x, float y)
{
gl.glUniform2f(wrapLocation(m_uniforms, name), x, y);
}
/**
* Use only if a GL instance is not available in the thread.
* @param uniform_name
* @param x
* @param y
*/
public void setUniform2f(String uniform_name, float x, float y)
{
Object[] params = new Object[]{ null, uniform_name, x, y };
@SuppressWarnings("rawtypes")
Class[] classes = new Class[]{ GL3.class, String.class, float.class, float.class };
m_methods.add(new ShaderMethod("setUniform2f", params, classes));
}
public void setUniform3f(GL3 gl, String name, float x, float y, float z)
{
gl.glUniform3f(wrapLocation(m_uniforms, name), x, y, z);
}
public void setUniform4f(GL3 gl, String name, float x, float y, float z, float w)
{
gl.glUniform4f(wrapLocation(m_uniforms, name), x, y, z, w);
}
public void setUniformMatrix3f(GL3 gl, boolean row_major, String name, float[] matrix)
{
gl.glUniformMatrix3fv(wrapLocation(m_uniforms, name), 1, row_major, matrix, 0);//true means row major order
}
public void setUniformMatrix4f(GL3 gl, boolean row_major, String name, float[] matrix)
{
gl.glUniformMatrix4fv(wrapLocation(m_uniforms, name), 1, row_major, matrix, 0);//true means row major order
}
public void update(GL3 gl)
{
enable(gl);
for(int i = 0; i < m_methods.size(); i++)
{
ShaderMethod shader_method = m_methods.get(i);
Method method = null;
try
{
method = getClass().getDeclaredMethod(shader_method.getMethodName(), shader_method.getClasses());
}
catch (NoSuchMethodException e) { e.printStackTrace(); System.exit(EXIT_CODE.SHADER_FAILURE.err_code); }
catch (SecurityException e) { e.printStackTrace(); System.exit(EXIT_CODE.SHADER_FAILURE.err_code); }
try
{
Object[] params = shader_method.getParams();
params[0] = gl;
method.invoke(this, params);
}
catch (IllegalAccessException e) { e.printStackTrace(); System.exit(EXIT_CODE.SHADER_FAILURE.err_code); }
catch (IllegalArgumentException e) { e.printStackTrace(); System.exit(EXIT_CODE.SHADER_FAILURE.err_code); }
catch (InvocationTargetException e) { e.printStackTrace(); System.exit(EXIT_CODE.SHADER_FAILURE.err_code); }
}
m_methods.clear();
}
public boolean valid()
{
return m_valid;
}
private Integer wrapLocation(HashMap<String, Integer> map, String name)
{
Integer location = map.get(name);
if(location == null)
{
Logger.log(WARNING, "Could not bind shader var: " + name, getName());
return -1;
}
return location;
}
}
GL_INVALID_OPERATION is generated if texture was previously created with a target that doesn’t match that of target.
Check initialization and binding targets
Anyway, let me suggest you few tips about your code:
I see you are doing a lot of things in your own and also you have a couple of function that have one line. Be aware that this largely increase the error window… you may easily reduce overhead and dimension of that class, increasing readability and debugging, with a couple of things
you are using a very outdated jogl, I can see the old location of GL3, import javax.media.opengl.GL3;
if you can, you should upgrade to OpenGL 4, that offers also explicit location, this way you don’t have to deal at all with locations… Otherwise, unless you have to deal with tens of uniforms, HashMap is overkill, just use an integer array indexed by semantic final ints
you should use samplers
if your textures have all the same size and level (mipmap) number, you may use directly a sampler2DArray
bindUniformBufferObjects doesn’t actually bind them, it just retrieves the uniform block index (same for bindUniforms)… actually since you are supposed to bind it just once, you may completly skip the uniform block index in this way
you may remove completely buildShader, checkForProgramErrors and checkForShaderErrors and delegate the whole shit to jogl in few lines of code, like here
you should avoid all one line functions, such as cleanup, disable, enable, setUniform1f, setUniform1fv, setUniform1i, setUniform1iv, setUniform2f, setUniform3f, setUniform4f, setUniformMatrix3f, setUniformMatrix4f
in init(gl3), you link your program and then //Bind the attributes before we link
Still confused as to why this is happening, but I have a work around.
If I call glActiveTexture() 0-MAX and bind either a texture or 0 (where MAX is the maximum supported texture units) on the first render pass then the subsequent render passes do not break.
@elect, thanks for pointing out I needed to upgrade JOGL.
Notes:
To clarify by render pass I mean calling glDrawElements() not rendering a new frame.
MAX could also be the maximum amount of textures used in all render passes.
Simplified and clean code may really help you a lot, don’t underestimate it
glActiveTexture defines the “hook” on which any sequent texture binding call will bound the texture of the given id to.
This is one of the best resource you can access over this topic, I suggest you to read it carefully until you understand how texturing works in GL (3.x)
It is quite important to enable your program before binding textures…I was enabling the program after binding textures in my batch renderers which caused the problems.