I've been trying to use shaders, but can't get them working. I create and compile the shaders (no error there), attach them to a program, link the program and validate it. No errors. I then use the program and draw a triangle, but the triangle is always drawn using fixed function mode rather than using my basic shader program. It doesn't matter if I use immediate mode, vertex arrays, or VBOs, the shader is never used. If you could help me determine the issue I would appreciate it a lot.
The code:
Main class
package net.midnightindie.dungeon;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Frame;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import net.midnightindie.dungeon.render.ProgramManager;
import net.midnightindie.dungeon.util.WindowListener;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Controllers;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.util.glu.GLU;
public class Dungeon extends Applet implements Runnable
{
private static final long serialVersionUID = 1L;
private static final boolean isApplet = false;
protected Frame frame;
protected Canvas canvas;
protected int displayWidth;
protected int displayHeight;
protected boolean areControllersAvailable;
protected Thread gameThread;
protected boolean isRunning;
protected boolean isPaused;
protected long lastSecond;
protected long lastFrame;
protected int frames;
protected FloatBuffer vertexArray;
protected FloatBuffer vboVertexArray;
protected int vboVertexArrayHandle;
public Dungeon()
{
}
public void initialize()
{
displayWidth = 900;
displayHeight = 600;
CreateDisplay();
areControllersAvailable = false;
CreateInputDevices();
initializeGL();
ProgramManager.instance().createPrograms();
ProgramManager.instance().useProgram("basic");
initializeVertexArray();
initializeVBO();
lastSecond = lastFrame = System.currentTimeMillis();
frames = 0;
isRunning = true;
isPaused = false;
}
protected void initializeGL()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glClearDepth(1.0f);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLU.gluPerspective(45, displayWidth / displayHeight, 0.1f, 100);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
public void initializeVertexArray()
{
vertexArray = BufferUtils.createFloatBuffer(9);
vertexArray.put(-1).put(-1).put(0);
vertexArray.put(0).put(1).put(0);
vertexArray.put(1).put(-1).put(0);
vertexArray.flip();
}
public void initializeVBO()
{
vboVertexArray = BufferUtils.createFloatBuffer(9);
vboVertexArray.put(-1).put(-1).put(0);
vboVertexArray.put(0).put(1).put(0);
vboVertexArray.put(1).put(-1).put(0);
vboVertexArray.flip();
IntBuffer buffer = BufferUtils.createIntBuffer(1);
glGenBuffers(buffer);
vboVertexArrayHandle = buffer.get(0);
glBindBuffer(GL_ARRAY_BUFFER, vboVertexArrayHandle);
glBufferData(GL_ARRAY_BUFFER, vboVertexArray, GL_STATIC_DRAW);
}
public void cleanupVBO()
{
IntBuffer buffer = BufferUtils.createIntBuffer(1);
buffer.put(vboVertexArrayHandle);
buffer.flip();
glDeleteBuffers(buffer);
}
@SuppressWarnings("serial")
protected void CreateDisplay()
{
if (isApplet)
{
canvas = new Canvas()
{
public final void addNotify()
{
super.addNotify();
try
{
Display.setParent(this);
Display.create();
} catch (Exception ex)
{
}
}
public final void removeNotify()
{
Display.destroy();
super.removeNotify();
}
};
canvas.setSize(getWidth(), getHeight());
add(canvas);
canvas.setFocusable(true);
canvas.requestFocus();
canvas.setIgnoreRepaint(true);
setVisible(true);
}
else
{
frame = new Frame();
frame.setTitle("glint");
frame.setSize(displayWidth, displayHeight);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout());
frame.addWindowListener(new WindowListener(this, gameThread));
canvas = new Canvas();
frame.add(canvas, "Center");
frame.setVisible(true);
try
{
Display.setParent(canvas);
Display.create();
} catch (LWJGLException ex)
{
HandleError(ex.getMessage());
}
}
}
public void CreateInputDevices()
{
try
{
Keyboard.create();
Mouse.create();
Mouse.setGrabbed(true);
} catch (LWJGLException ex)
{
HandleError("Unable to create input devices");
}
try
{
Controllers.create();
areControllersAvailable = true;
} catch (LWJGLException ex)
{
areControllersAvailable = false;
}
}
public void Start()
{
gameThread = new Thread(this, "glint");
gameThread.start();
}
public void Pause()
{
isPaused = true;
}
public void Resume()
{
isPaused = false;
}
public void Stop()
{
isRunning = false;
}
public void Destroy()
{
cleanupVBO();
Keyboard.destroy();
Mouse.setGrabbed(false);
Mouse.destroy();
Controllers.destroy();
Display.destroy();
System.out.println("TERMINATED");
}
public void PollInputDevices()
{
if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) isRunning = false;
if (Keyboard.isKeyDown(Keyboard.KEY_F11)) Mouse.setGrabbed(true);
if (Keyboard.isKeyDown(Keyboard.KEY_F12)) Mouse.setGrabbed(false);
}
public void Update()
{
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastFrame;
if (currentTime - lastSecond >= 1000)
{
System.out.println("FPS: " + frames);
lastSecond = currentTime;
frames = 0;
}
lastFrame = currentTime;
frames++;
}
public void immediateModeTriangle()
{
glBegin(GL_TRIANGLES);
glColor3f(0, 0, 0);
glVertex3f(1, 0, -4);
glColor3f(1, 0, 0);
glVertex3f(0, 1, -4);
glColor3f(0, 0, 0);
glVertex3f(-1, 0, -4);
glEnd();
}
public void vertexArrayTriangle()
{
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, 0, vertexArray);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
}
public void vboTriangle()
{
glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, vboVertexArrayHandle);
glVertexPointer(3, GL_FLOAT, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
}
public void Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3f(1, 0, 0);
immediateModeTriangle();
// glTranslatef(0, 0, -4);
// vertexArrayTriangle();
// glTranslatef(0, 0, -4);
// vboTriangle();
}
public void run()
{
initialize();
while (isRunning)
{
if (isPaused)
{
try
{
Thread.currentThread();
Thread.sleep(100);
} catch (InterruptedException ex)
{
}
continue;
}
PollInputDevices();
Update();
Render();
Display.update();
}
Destroy();
System.exit(0);
}
public void HandleError(String errorMessage)
{
System.out.println("[ERROR] " + errorMessage);
Stop();
}
public void start()
{
Start();
}
public void stop()
{
Stop();
}
public static void main(String[] args)
{
System.out.println("Working Directory: " + System.getProperty("user.dir"));
Dungeon glint = new Dungeon();
glint.Start();
}
}
Program Manager
package net.midnightindie.dungeon.render;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.lwjgl.BufferUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
public class ProgramManager
{
private static ProgramManager instance = null;
public static ProgramManager instance()
{
if (instance == null)
{
instance = new ProgramManager();
}
return instance;
}
private Hashtable<String, Shader> vertexShaders;
private Hashtable<String, Shader> fragmentShaders;
private Hashtable<String, Program> programs;
private ProgramManager()
{
vertexShaders = new Hashtable<String, Shader>();
fragmentShaders = new Hashtable<String, Shader>();
programs = new Hashtable<String, Program>();
}
public void createPrograms()
{
programs.clear();
int numPrograms = 0;
try
{
File filePrograms = new File("res/programs.xml");
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(filePrograms);
NodeList nodeListPrograms = doc.getElementsByTagName("program");
for (int i = 0; i < nodeListPrograms.getLength(); i++)
{
Node nodeProgram = nodeListPrograms.item(i);
if (nodeProgram.getNodeType() == Node.ELEMENT_NODE)
{
Element element = (Element) nodeProgram;
NodeList nodeListName = element.getElementsByTagName("name").item(0).getChildNodes();
String programName = ((Node) nodeListName.item(0)).getNodeValue();
ArrayList<Shader> vshaders = new ArrayList<Shader>();
NodeList nodeListVertexShaders = element.getElementsByTagName("vertex");
for (int j = 0; j < nodeListVertexShaders.getLength(); j++)
{
NodeList nodeListVertexShadersValue = nodeListVertexShaders.item(j).getChildNodes();
String shaderName = ((Node) nodeListVertexShadersValue.item(0)).getNodeValue();
if (!vertexShaders.containsKey(shaderName))
{
loadVertexShader(shaderName);
}
vshaders.add(vertexShaders.get(shaderName));
}
ArrayList<Shader> fshaders = new ArrayList<Shader>();
NodeList nodeListFragmentShaders = element.getElementsByTagName("fragment");
for (int j = 0; j < nodeListFragmentShaders.getLength(); j++)
{
NodeList nodeListFragmentShadersValue = nodeListFragmentShaders.item(j).getChildNodes();
String shaderName = ((Node) nodeListFragmentShadersValue.item(0)).getNodeValue();
if (!fragmentShaders.containsKey(shaderName))
{
loadFragmentShader(shaderName);
}
fshaders.add(fragmentShaders.get(shaderName));
}
programs.put(programName, new Program(vshaders, fshaders));
numPrograms++;
}
}
} catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
System.exit(0);
}
System.out.println("Number of programs created: " + numPrograms);
vertexShaders.clear();
fragmentShaders.clear();
}
private void loadVertexShader(String name)
{
try
{
File file = new File("res/shaders/" + name + ".vert");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer shaderSource = BufferUtils.createByteBuffer((int) fc.size());
fc.read(shaderSource);
vertexShaders.put(name, new Shader(GL_VERTEX_SHADER, shaderSource));
System.out.println("Loaded vertex shader '" + name + "'");
} catch (Exception ex)
{
System.out.println("Error creating vertex shader '" + name + "'");
}
}
private void loadFragmentShader(String name)
{
try
{
File file = new File("res/shaders/" + name + ".frag");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer shaderSource = BufferUtils.createByteBuffer((int) fc.size());
fc.read(shaderSource);
fragmentShaders.put(name, new Shader(GL_FRAGMENT_SHADER, shaderSource));
System.out.println("Loaded fragment shader '" + name + "'");
} catch (Exception ex)
{
System.out.println("Error creating fragment shader '" + name + "'");
}
}
public void useProgram(String name)
{
programs.get(name).use();
System.out.println("Now using program '" + name + "'");
}
class Shader
{
public final int handle;
public Shader(int type, ByteBuffer source)
{
handle = glCreateShader(type);
glShaderSource(handle, source);
glCompileShader(handle);
if (glGetShader(handle, GL_COMPILE_STATUS) == GL_FALSE)
{
System.out.println("shader compilation failed.");
}
}
}
class Program
{
public final int handle;
public Program(ArrayList<Shader> vshaders, ArrayList<Shader> fshaders)
{
handle = glCreateProgram();
for (int i = 0; i < vshaders.size(); i++)
{
glAttachShader(handle, vshaders.get(i).handle);
}
for (int i = 0; i < fshaders.size(); i++)
{
glAttachShader(handle, fshaders.get(i).handle);
}
glLinkProgram(handle);
glValidateProgram(handle);
}
public void use()
{
glUseProgram(handle);
}
}
}
Vertex Shader
void main()
{
gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
}
also tried
main()
{
gl_Position = ftransform();
}
Fragment Shader
void main()
{
gl_FragColor = vec4(0, 1, 0, 1);
}
You need to bind the shader when you render for it to work, you know.
As in glUseProgram(), correct? Or are you referring to something else? I do call my ProgramManager's useProgram() (which does a glUseProgram()) in my initialize method. Just now I also tried putting the useProgram() call in my actual render function as well, just to see if it made a difference. It didn't.
public void initialize()
{
...
ProgramManager.instance().createPrograms();
ProgramManager.instance().useProgram("basic");
...
}
also tried
public void Render()
{
...
glUseProgram(ProgramManager.instance().getProgramHandle("basic"));
immediateModeTriangle();
...
}
I figured it out! When I was loading the shaders (using a ByteBuffer) I forgot to flip the buffer after loading the file. In case it helps someone in the future, the correct code is below. The only change is the line shaderSource.flip();
New ProgramManager:
package net.midnightindie.dungeon.render;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.lwjgl.BufferUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
public class ProgramManager
{
private static ProgramManager instance = null;
public static ProgramManager instance()
{
if (instance == null)
{
instance = new ProgramManager();
}
return instance;
}
private Hashtable<String, Shader> vertexShaders;
private Hashtable<String, Shader> fragmentShaders;
private Hashtable<String, Program> programs;
private ProgramManager()
{
vertexShaders = new Hashtable<String, Shader>();
fragmentShaders = new Hashtable<String, Shader>();
programs = new Hashtable<String, Program>();
}
public void createPrograms()
{
programs.clear();
int numPrograms = 0;
try
{
File filePrograms = new File("res/programs.xml");
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(filePrograms);
NodeList nodeListPrograms = doc.getElementsByTagName("program");
for (int i = 0; i < nodeListPrograms.getLength(); i++)
{
Node nodeProgram = nodeListPrograms.item(i);
if (nodeProgram.getNodeType() == Node.ELEMENT_NODE)
{
Element element = (Element) nodeProgram;
NodeList nodeListName = element.getElementsByTagName("name").item(0).getChildNodes();
String programName = ((Node) nodeListName.item(0)).getNodeValue();
ArrayList<Shader> vshaders = new ArrayList<Shader>();
NodeList nodeListVertexShaders = element.getElementsByTagName("vertex");
for (int j = 0; j < nodeListVertexShaders.getLength(); j++)
{
NodeList nodeListVertexShadersValue = nodeListVertexShaders.item(j).getChildNodes();
String shaderName = ((Node) nodeListVertexShadersValue.item(0)).getNodeValue();
if (!vertexShaders.containsKey(shaderName))
{
loadVertexShader(shaderName);
}
vshaders.add(vertexShaders.get(shaderName));
}
ArrayList<Shader> fshaders = new ArrayList<Shader>();
NodeList nodeListFragmentShaders = element.getElementsByTagName("fragment");
for (int j = 0; j < nodeListFragmentShaders.getLength(); j++)
{
NodeList nodeListFragmentShadersValue = nodeListFragmentShaders.item(j).getChildNodes();
String shaderName = ((Node) nodeListFragmentShadersValue.item(0)).getNodeValue();
if (!fragmentShaders.containsKey(shaderName))
{
loadFragmentShader(shaderName);
}
fshaders.add(fragmentShaders.get(shaderName));
}
programs.put(programName, new Program(vshaders, fshaders));
numPrograms++;
}
}
} catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
System.exit(0);
}
System.out.println("Number of programs created: " + numPrograms);
vertexShaders.clear();
fragmentShaders.clear();
}
private void loadVertexShader(String name)
{
try
{
File file = new File("res/shaders/" + name + ".vert");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer shaderSource = BufferUtils.createByteBuffer((int) fc.size());
fc.read(shaderSource);
shaderSource.flip();
vertexShaders.put(name, new Shader(GL_VERTEX_SHADER, shaderSource));
System.out.println("Loaded vertex shader '" + name + "'");
} catch (Exception ex)
{
System.out.println("Error creating vertex shader '" + name + "'");
}
}
private void loadFragmentShader(String name)
{
try
{
File file = new File("res/shaders/" + name + ".frag");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer shaderSource = BufferUtils.createByteBuffer((int) fc.size());
fc.read(shaderSource);
shaderSource.flip();
fragmentShaders.put(name, new Shader(GL_FRAGMENT_SHADER, shaderSource));
System.out.println("Loaded fragment shader '" + name + "'");
} catch (Exception ex)
{
System.out.println("Error creating fragment shader '" + name + "'");
}
}
public void useProgram(String name)
{
programs.get(name).use();
System.out.println("Now using program '" + name + "'");
}
class Shader
{
public final int handle;
public Shader(int type, ByteBuffer source)
{
handle = glCreateShader(type);
glShaderSource(handle, source);
glCompileShader(handle);
if (glGetShader(handle, GL_COMPILE_STATUS) == GL_FALSE)
{
System.out.println("shader compilation failed.");
}
}
}
class Program
{
public final int handle;
public Program(ArrayList<Shader> vshaders, ArrayList<Shader> fshaders)
{
handle = glCreateProgram();
for (int i = 0; i < vshaders.size(); i++)
{
glAttachShader(handle, vshaders.get(i).handle);
}
for (int i = 0; i < fshaders.size(); i++)
{
glAttachShader(handle, fshaders.get(i).handle);
}
glLinkProgram(handle);
glValidateProgram(handle);
}
public void use()
{
glUseProgram(handle);
}
}
}