mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-08 12:51:51 +09:00
First commit
Former-commit-id: 9340873f9cfb15264004c32d6e4b8f8bd6828d94 Former-commit-id: 1916747c109876aa064412e01204c3aeda9bbbc0
This commit is contained in:
284
src/shader/MultiTex.java
Executable file
284
src/shader/MultiTex.java
Executable file
@@ -0,0 +1,284 @@
|
||||
package shader;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Renderable;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.opengl.InternalTextureLoader;
|
||||
import org.newdawn.slick.opengl.Texture;
|
||||
|
||||
/**
|
||||
* Class to support the concept of a single artifact being
|
||||
* comprised of multiple image resources.</br>
|
||||
* For example a colourmap, normalmap, diffusemap, and specularmap.
|
||||
* This is currently extremely buggy, and I don't know why some
|
||||
* things have to be the way the are.
|
||||
* @author Chronocide (Jeremy Klix)
|
||||
*
|
||||
*
|
||||
* All drawing is done starting from the top left vertex and
|
||||
* moving counter clockwise.</br>
|
||||
*/
|
||||
//TODO Make interface feel a little more like the familiar Image class
|
||||
//TODO Determine a method of dealing with the case were textures
|
||||
//are not all the same size. For instance should textures be
|
||||
//stretched, tiled, clamped?
|
||||
//TODO Way of handling images larger then the supporting cards
|
||||
//max texture size ala Slicks BigImage class.
|
||||
//TODO Needs way more attention to documenting inheritance.
|
||||
//TODO Test using different numbers of textures.
|
||||
public class MultiTex implements Renderable{
|
||||
private static int units = -1;
|
||||
|
||||
/** The top left corner identifier */
|
||||
public static final int TOP_LEFT = 0;
|
||||
/** The top right corner identifier */
|
||||
public static final int BOTTOM_LEFT = 1;
|
||||
/** The bottom left corner identifier */
|
||||
public static final int BOTTOM_RIGHT = 3;
|
||||
/** The bottom right corner identifier */
|
||||
public static final int TOP_RIGHT = 2;
|
||||
|
||||
|
||||
private List<Texture> textures;
|
||||
|
||||
private int primaryTextureIndex = 0;
|
||||
//Width and height based off primary texture loaded
|
||||
private float imgWidth, imgHeight;
|
||||
//Primary texture width and height clamped between 0 and 1
|
||||
private float texWidth, texHeight;
|
||||
|
||||
|
||||
private float[] normals = new float[]{0,0,1,
|
||||
0,0,1,
|
||||
0,0,1,
|
||||
0,0,1};
|
||||
private float[] colours = new float[]{1,1,1,1,
|
||||
1,1,1,1,
|
||||
1,1,1,1,
|
||||
1,1,1,1};
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>MultiTex</tt> object using the textures
|
||||
* identified in <tt>textures</tt>.</br>
|
||||
* The index of the textures in the list will be the texture unit
|
||||
* that the texture is bound to.</br>
|
||||
* @param textures a list of paths to the textures to use.
|
||||
* @throws SlickException If <tt>textures.size()</tt> is greater
|
||||
* than the maximum number of texture units.
|
||||
*/
|
||||
public MultiTex(List<String> textures)throws SlickException{
|
||||
//Check how many texture units are supported
|
||||
if(units==-1){
|
||||
units = GL11.glGetInteger(GL20.GL_MAX_TEXTURE_IMAGE_UNITS);
|
||||
System.out.println(units);
|
||||
}
|
||||
if(units < textures.size()){
|
||||
throw new UnsupportedOperationException("You attempted to " +
|
||||
"create an artifact with " + textures.size() +
|
||||
" textures, but your environment only supports " +
|
||||
units + " texure image units.");
|
||||
}
|
||||
|
||||
//Create texture list
|
||||
this.textures = new ArrayList<Texture>(textures.size());
|
||||
|
||||
|
||||
|
||||
//Load textures into texture list.
|
||||
InternalTextureLoader itl = InternalTextureLoader.get();
|
||||
for(int i = 0; i<textures.size(); i++){
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
try{
|
||||
this.textures.add(itl.getTexture(textures.get(i),
|
||||
false,
|
||||
GL11.GL_LINEAR,
|
||||
null));
|
||||
}catch(IOException e){
|
||||
throw new SlickException(e.getMessage());
|
||||
}
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
//FIXME pretty sure there is a rather serious problem here.
|
||||
//Since the TextureLoader used keeps track of previously loaded
|
||||
//textures, and binds them to a unit at creation. If a single
|
||||
//image is loaded twice to two different Texture Units, it may
|
||||
//not actually be associated with the correct unit on the
|
||||
//second load. This is because the TextureLoader will simply
|
||||
//return the earlier loaded texture.
|
||||
|
||||
//Reset current texture unit to 0
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
imgWidth = this.textures.get(primaryTextureIndex).getImageWidth();
|
||||
imgHeight = this.textures.get(primaryTextureIndex).getImageHeight();
|
||||
texWidth = this.textures.get(primaryTextureIndex).getWidth();
|
||||
texHeight = this.textures.get(primaryTextureIndex).getHeight();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MultiTex(String[] textures) throws SlickException{
|
||||
this(Arrays.asList(textures));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MultiTex(String t1, String t2)throws SlickException{
|
||||
this(new String[]{t1,t2});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* When extending please note that this method relies on the
|
||||
* private method drawEmbedded.</br>
|
||||
*/
|
||||
public void draw(float x, float y){
|
||||
draw(x, y, x + imgWidth, y+imgHeight,
|
||||
0, 0, imgWidth, imgHeight);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Draw a section of this MultTex at a particular location and
|
||||
* scale on the screen.</br>
|
||||
*
|
||||
* This is the draw method that all other overloaded draw
|
||||
* methods eventually evoke.</br>
|
||||
*
|
||||
* @param x1
|
||||
* @param y1
|
||||
* @param x2
|
||||
* @param y2
|
||||
* @param sx1
|
||||
* @param sy1
|
||||
* @param sx2
|
||||
* @param sy2
|
||||
*/
|
||||
public void draw(float x1, float y1, float x2, float y2,
|
||||
float sx1, float sy1, float sx2, float sy2){
|
||||
|
||||
//Bind textures to their correct locations
|
||||
for(int i = 0; i < textures.size(); i++){
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textures.get(i).getTextureID());
|
||||
}
|
||||
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
drawEmbedded(x1, y1, x2, y2,
|
||||
sx1, sy1, sx2, sy2);
|
||||
GL11.glEnd();
|
||||
|
||||
//Clean up texture setting to allow basic slick to operate correctly.
|
||||
for(int i = textures.size()-1; i>=0; i--){
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0+i);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void draw(float x1, float y1, float x2, float y2,
|
||||
float sx1, float sy1, float sx2, float sy2,
|
||||
Color c){
|
||||
float[] bu = colours;//Save the colour state
|
||||
|
||||
setColour(c);
|
||||
draw(x1, y1, x2, y2, sx1, sy1, sx2, sy2);
|
||||
|
||||
colours = bu;//Restore the colour state
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the colour of a given corner.</br>
|
||||
* Note that this will have an effect only if: the
|
||||
* fixed pixel pipeline is being used; or the applied shader
|
||||
* takes the vertex colour into account.</br>
|
||||
* @param corner
|
||||
* @param c
|
||||
*/
|
||||
public void setColour(int corner, Color c){
|
||||
colours[corner*4 + 0] = c.r;
|
||||
colours[corner*4 + 1] = c.g;
|
||||
colours[corner*4 + 2] = c.b;
|
||||
colours[corner*4 + 3] = c.a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the colour of all four corners.</br>
|
||||
* @param c
|
||||
*/
|
||||
public void setColour(Color c){
|
||||
for(int i=0; i<4; i++){
|
||||
setColour(i, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void drawEmbedded(float x1, float y1, float x2, float y2,
|
||||
float sx1, float sy1, float sx2, float sy2){
|
||||
//TODO reduce code duplication need to produce sequence 0,1,3,2
|
||||
|
||||
//TOP LEFT
|
||||
for(int i=0; i<textures.size(); i++){
|
||||
GL13.glMultiTexCoord2f(GL13.GL_TEXTURE0 + i,
|
||||
(sx1/imgWidth) * texWidth,
|
||||
(sy1/imgHeight)* texHeight);
|
||||
}
|
||||
GL11.glColor4f(colours[0], colours[1], colours[2], colours[3]);
|
||||
GL11.glNormal3f(normals[0], normals[1], normals[2]);
|
||||
GL11.glVertex3f(x1, y1, 0);
|
||||
|
||||
//BOTTOM LEFT
|
||||
for(int i=0; i<textures.size(); i++){
|
||||
GL13.glMultiTexCoord2f(GL13.GL_TEXTURE0 + i,
|
||||
(sx1/imgWidth) * texWidth,
|
||||
(sy2/imgHeight)* texHeight);
|
||||
}
|
||||
GL11.glColor4f(colours[3], colours[5], colours[6], colours[7]);
|
||||
GL11.glNormal3f(normals[3], normals[4], normals[5]);
|
||||
GL11.glVertex3f(x1, y2, 0);
|
||||
|
||||
//BOTTOM RIGHT
|
||||
for(int i=0; i<textures.size(); i++){
|
||||
GL13.glMultiTexCoord2f(GL13.GL_TEXTURE0 + i,
|
||||
(sx2/imgWidth) * texWidth,
|
||||
(sy2/imgHeight)* texHeight);
|
||||
}
|
||||
GL11.glColor4f(colours[8], colours[9], colours[10], colours[11]);
|
||||
GL11.glNormal3f(normals[6], normals[7], normals[8]);
|
||||
GL11.glVertex3f(x2, y2, 0);
|
||||
|
||||
//TOP RIGHT
|
||||
for(int i=0; i<textures.size(); i++){
|
||||
GL13.glMultiTexCoord2f(GL13.GL_TEXTURE0 + i,
|
||||
(sx2/imgWidth) * texWidth,
|
||||
(sy1/imgHeight)* texHeight);
|
||||
}
|
||||
GL11.glColor4f(colours[12], colours[13], colours[14], colours[15]);
|
||||
GL11.glNormal3f(normals[9], normals[10], normals[11]);
|
||||
GL11.glVertex3f(x2, y1, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
453
src/shader/Shader.java
Executable file
453
src/shader/Shader.java
Executable file
@@ -0,0 +1,453 @@
|
||||
package shader;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Class used to use and access shaders without having to deal
|
||||
* with all of the fidly openGL bits.
|
||||
* @author Chronocide (Jeremy Klix)
|
||||
*
|
||||
*/
|
||||
public class Shader {
|
||||
public static final int BRIEF = 128;
|
||||
public static final int MODERATE = 512;
|
||||
public static final int VERBOSE = 1024;
|
||||
private static final int NOT_LOADED = -1;
|
||||
private static final String ERR_LOCATION =
|
||||
"Warning: variable %s could not be found. " +
|
||||
"Ensure the name is spelled correctly\n";
|
||||
private static int logging = MODERATE;
|
||||
|
||||
private ShaderResourceManager srm;
|
||||
/**
|
||||
* ID of the <tt>Shader</tt>. A Shader may have programID of
|
||||
* -1 only before construction is completed, or
|
||||
* after the <tt>Shader</tt> is deleted
|
||||
*/
|
||||
private int programID = NOT_LOADED;
|
||||
private Map<String, ShaderVariable> vars = new HashMap<String, ShaderVariable>();
|
||||
|
||||
|
||||
private Shader(ShaderResourceManager srm,
|
||||
Collection<String> vertex,
|
||||
Collection<String> fragment)throws SlickException{
|
||||
this.srm = srm;
|
||||
StringBuilder errorMessage = new StringBuilder();
|
||||
|
||||
programID = GL20.glCreateProgram();
|
||||
|
||||
int[] shaderIds = new int[vertex.size() + fragment.size()];
|
||||
int index = 0;
|
||||
|
||||
//Load Vertex Shaders
|
||||
for(String vertShader: vertex){
|
||||
int vsid = srm.getVertexShaderID(vertShader);
|
||||
srm.createProgramShaderDependancy(programID, vsid);
|
||||
|
||||
//Add to shader ids array
|
||||
shaderIds[index] = vsid;
|
||||
index++;
|
||||
|
||||
//Check for errors with shader
|
||||
if(!compiledSuccessfully(vsid)){
|
||||
errorMessage.append("Vertex Shader ");
|
||||
errorMessage.append(vertShader);
|
||||
errorMessage.append(" failed to compile.\n");
|
||||
errorMessage.append(getShaderInfoLog(vsid));
|
||||
errorMessage.append("\n\n");
|
||||
}
|
||||
|
||||
scanSource(vertShader);
|
||||
}
|
||||
|
||||
|
||||
//Load Fragment Shaders
|
||||
for(String fragShader: fragment){
|
||||
int fsid = srm.getFragementShaderID(fragShader);
|
||||
srm.createProgramShaderDependancy(programID, fsid);
|
||||
|
||||
//Add to shader ids array
|
||||
shaderIds[index] = fsid;
|
||||
index++;
|
||||
|
||||
//Check for errors with shader
|
||||
if(!compiledSuccessfully(fsid)){
|
||||
errorMessage.append("Fragment Shader ");
|
||||
errorMessage.append(fragShader);
|
||||
errorMessage.append(" failed to compile.\n");
|
||||
errorMessage.append(getShaderInfoLog(fsid));
|
||||
errorMessage.append("\n\n");
|
||||
}
|
||||
|
||||
scanSource(fragShader);
|
||||
}
|
||||
|
||||
//Attach shaders to program
|
||||
for(int i=0; i<index; i++){
|
||||
GL20.glAttachShader(programID, shaderIds[i]);
|
||||
}
|
||||
//Link program
|
||||
GL20.glLinkProgram(programID);
|
||||
if(!linkedSuccessfully()){
|
||||
errorMessage.append("Linking Error\n");
|
||||
errorMessage.append(getProgramInfoLog());
|
||||
errorMessage.append("\n\n");
|
||||
}
|
||||
|
||||
if(errorMessage.length()!=0){
|
||||
errorMessage.insert(0, "Could not compile shader.\n");
|
||||
srm.removeProgram(programID);
|
||||
programID = -1;
|
||||
errorMessage.append("Stack Trace:");
|
||||
throw new SlickException(errorMessage.toString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory method to create a new Shader.
|
||||
* @param vertexFileName
|
||||
* @param fragmentFileName
|
||||
* @return
|
||||
* @throws SlickException
|
||||
*/
|
||||
public static Shader makeShader(String vertexFileName,
|
||||
String fragmentFileName)throws SlickException{
|
||||
ArrayList<String> l1 = new ArrayList<String>();
|
||||
l1.add(vertexFileName);
|
||||
ArrayList<String> l2 = new ArrayList<String>();
|
||||
l2.add(fragmentFileName);
|
||||
|
||||
return new Shader(ShaderResourceManagerImpl.getSRM(),
|
||||
l1,
|
||||
l2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reverts GL context back to the fixed pixel pipeline.<br>
|
||||
*/
|
||||
public static void forceFixedShader(){
|
||||
GL20.glUseProgram(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of characters to be returned when printing
|
||||
* errors.</br> Suggested values are the constants
|
||||
* <tt>BRIEF</tt>, <tt>MODERATE</tt>, and <tt>VERBOSE</tt>.</br>
|
||||
* @param detailLevel number of characters to display for error
|
||||
* messages.
|
||||
*/
|
||||
public static void setLoggingDetail(int detailLevel){
|
||||
logging = detailLevel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Deletes this shader and unloads all free resources.</br>
|
||||
* TODO should this be called from <tt>finalise()</tt>, or is
|
||||
* that just asking for trouble?
|
||||
*/
|
||||
public void deleteShader(){
|
||||
srm.removeProgram(programID);
|
||||
programID = NOT_LOADED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this <tt>Shader</tt> has been deleted.</br>
|
||||
* @return true if this <tt>Shader</tt> has been deleted.</br>
|
||||
*/
|
||||
public boolean isDeleted(){
|
||||
return programID == NOT_LOADED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Activates the shader.</br>
|
||||
*/
|
||||
public void startShader(){
|
||||
if(programID == NOT_LOADED){
|
||||
throw new IllegalStateException("Cannot start shader; this" +
|
||||
" Shader has been deleted");
|
||||
}
|
||||
forceFixedShader(); //Not sure why this is necessary but it is.
|
||||
GL20.glUseProgram(programID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//UNIFORM SETTERS
|
||||
/**
|
||||
* Sets the value of the uniform integer Variable <tt>name</tt>.</br>
|
||||
* @param name the variable to set.
|
||||
* @param value the value to be set.
|
||||
*/
|
||||
public Shader setUniformIntVariable(String name, int value){
|
||||
return setUniformIntVariable(name, new int[]{value});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformIntVariable(String name, int v0, int v1){
|
||||
return setUniformIntVariable(name, new int[]{v0, v1});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformIntVariable(String name,
|
||||
int v0, int v1, int v2){
|
||||
return setUniformIntVariable(name, new int[]{v0, v1, v2});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformIntVariable(String name,
|
||||
int v0, int v1, int v2, int v3){
|
||||
return setUniformIntVariable(name, new int[]{v0, v1, v2, v3});
|
||||
}
|
||||
|
||||
|
||||
public Shader setUniformIntVariable(String name, int[] values){
|
||||
ShaderVariable var = vars.get(name);
|
||||
if(var==null){
|
||||
printError(name);
|
||||
}else{
|
||||
var.setUniformValue(values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of the uniform integer Variable
|
||||
* <tt>name</tt>.</br>
|
||||
* @param name the variable to set.
|
||||
* @param value the value to be set.
|
||||
*/
|
||||
public Shader setUniformFloatVariable(String name, float value){
|
||||
return setUniformFloatVariable(name, new float[]{value});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformFloatVariable(String name,
|
||||
float v0, float v1){
|
||||
return setUniformFloatVariable(name, new float[]{v0, v1});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformFloatVariable(String name,
|
||||
float v0, float v1, float v2){
|
||||
return setUniformFloatVariable(name, new float[]{v0, v1, v2});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformFloatVariable(String name,
|
||||
float v0, float v1,
|
||||
float v2, float v3){
|
||||
return setUniformFloatVariable(name, new float[]{v0, v1, v2, v3});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Shader setUniformFloatVariable(String name, float[] values){
|
||||
ShaderVariable var = vars.get(name);
|
||||
if(var==null){
|
||||
printError(name);
|
||||
}else{
|
||||
var.setUniformValue(values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO implement using ShaderVariable
|
||||
//TODO Test
|
||||
public Shader setUniformMatrix(String name,
|
||||
boolean transpose,
|
||||
float[][] matrix){
|
||||
//Convert matrix format
|
||||
FloatBuffer matBuffer = matrixPrepare(matrix);
|
||||
|
||||
//Get uniform location
|
||||
int location = GL20.glGetUniformLocation(programID, name);
|
||||
printError(name);
|
||||
|
||||
//determine correct matrixSetter
|
||||
switch(matrix.length){
|
||||
case 2: GL20.glUniformMatrix2(location, transpose, matBuffer);
|
||||
break;
|
||||
case 3: GL20.glUniformMatrix3(location, transpose, matBuffer);
|
||||
break;
|
||||
case 4: GL20.glUniformMatrix4(location, transpose, matBuffer);
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private FloatBuffer matrixPrepare(float[][] matrix){
|
||||
//Check argument validity
|
||||
if(matrix==null){
|
||||
throw new IllegalArgumentException("The matrix may not be null");
|
||||
}
|
||||
int row = matrix.length;
|
||||
if(row<2){
|
||||
throw new IllegalArgumentException("The matrix must have at least 2 rows.");
|
||||
}
|
||||
int col = matrix[0].length;
|
||||
if(col!=row){
|
||||
throw new IllegalArgumentException("The matrix must have an equal number of rows and columns.");
|
||||
}
|
||||
float[] unrolled = new float[row*col];
|
||||
|
||||
for(int i=0;i<row;i++){
|
||||
for(int j=0;j<col;j++){
|
||||
unrolled[i*col+j] = matrix[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
//TODO FloatBuffer creation here is probably broken
|
||||
return FloatBuffer.wrap(unrolled);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void printError(String varName){
|
||||
System.err.printf(ERR_LOCATION, varName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the shader compiled successfully.</br>
|
||||
* @param shaderID
|
||||
* @return true if the shader compiled successfully.</br>
|
||||
*/
|
||||
private boolean compiledSuccessfully(int shaderID){
|
||||
return GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS)==GL11.GL_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the shader program linked successfully.</br>
|
||||
* @return true if the shader program linked successfully.</br>
|
||||
*/
|
||||
private boolean linkedSuccessfully(){
|
||||
int test = GL20.glGetShader(programID, GL20.GL_LINK_STATUS);
|
||||
return true;
|
||||
// return GL20.glGetShader(programID, GL20.GL_LINK_STATUS)==GL11.GL_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String getShaderInfoLog(int shaderID){
|
||||
return GL20.glGetShaderInfoLog(shaderID, logging).trim();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String getProgramInfoLog(){
|
||||
return GL20.glGetProgramInfoLog(programID, logging).trim();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void scrapeVariables(String varLine){
|
||||
ShaderVariable.Qualifier qualifier = null;
|
||||
ShaderVariable.Type type = null;
|
||||
String name = "";
|
||||
int vecSize = 1; // if a vector the
|
||||
int size = 1; //If array size of array
|
||||
|
||||
String str;
|
||||
Scanner scanner = new Scanner(varLine);
|
||||
scanner.useDelimiter("[\\s,]++");
|
||||
|
||||
//Determine qualifier
|
||||
qualifier = ShaderVariable.Qualifier.fromString(scanner.next());
|
||||
|
||||
//Determine type
|
||||
str = scanner.next();
|
||||
if(str.equals("float")){
|
||||
type = ShaderVariable.Type.FLOAT;
|
||||
}else if(str.matches("[u]?int|sampler[123]D")){
|
||||
type = ShaderVariable.Type.INTEGER;
|
||||
}else if(str.equals("bool")){
|
||||
type = ShaderVariable.Type.BOOLEAN;
|
||||
}else if(str.matches("[bdiu]?vec[234]")){
|
||||
char c = str.charAt(0);
|
||||
switch(c){
|
||||
case 'b':
|
||||
type = ShaderVariable.Type.BOOLEAN; break;
|
||||
case 'd':
|
||||
type = ShaderVariable.Type.DOUBLE; break;
|
||||
case 'i':
|
||||
case 'u':
|
||||
type = ShaderVariable.Type.INTEGER; break;
|
||||
case 'v':
|
||||
type = ShaderVariable.Type.FLOAT; break;
|
||||
}
|
||||
|
||||
str = str.substring(str.length()-1);
|
||||
vecSize = Integer.parseInt(str);
|
||||
}
|
||||
|
||||
|
||||
//Determine variable names
|
||||
while(scanner.hasNext("[\\w_]+[\\w\\d_]*(\\[\\d+\\])?")){
|
||||
name = scanner.next();
|
||||
if(name.contains("]")){
|
||||
String sub = name.substring(name.indexOf('[')+1, name.length()-1);
|
||||
size = Integer.parseInt(sub);
|
||||
name = name.substring(0, name.indexOf('[')).trim();
|
||||
}
|
||||
|
||||
ShaderVariable var =
|
||||
new ShaderVariable(programID,
|
||||
name, qualifier, type, vecSize, size);
|
||||
vars.put(var.name, var);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void scanSource(String filename){
|
||||
Scanner scanner = new Scanner(ResourceLoader.getResourceAsStream(filename));
|
||||
scanner.useDelimiter(";|\\{|\\}");
|
||||
while(scanner.hasNext()){
|
||||
while(scanner.hasNext("\\s*?(uniform|attribute|varying).*")){
|
||||
scrapeVariables(scanner.next().trim());
|
||||
}
|
||||
scanner.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
197
src/shader/ShaderManagerImpl.java
Executable file
197
src/shader/ShaderManagerImpl.java
Executable file
@@ -0,0 +1,197 @@
|
||||
package shader;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
/**
|
||||
* A simple class used to prevent duplicate shaders from
|
||||
* being loaded and compiled onto the video card.</br>
|
||||
* @author Chronocide (Jeremy Klix)
|
||||
*/
|
||||
class ShaderResourceManagerImpl implements ShaderResourceManager{
|
||||
private static final ShaderResourceManager SRM = new ShaderResourceManagerImpl();
|
||||
|
||||
|
||||
private Map<String, Integer> shaderMap = new HashMap<String, Integer>();
|
||||
private Map<Integer, Set<Integer>> shaderToPrograms = new HashMap<Integer, Set<Integer>>(); //for every shader lists all the programs that use it
|
||||
private Map<Integer, Set<Integer>> programToShaders = new HashMap<Integer, Set<Integer>>(); //for every program lists the shaders it uses
|
||||
|
||||
|
||||
//Constructor
|
||||
private ShaderResourceManagerImpl(){
|
||||
//Private Constructor to prevent external extension
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Factory Method
|
||||
static ShaderResourceManager getSRM(){
|
||||
return SRM;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a shader id for a given fragment shader, generating
|
||||
* a new id if necessary.</br>
|
||||
*
|
||||
* @param fragmentFileName the fragment shader to fetch an id for.
|
||||
* @returns shaderID for a given fragment shader.</br>
|
||||
*/
|
||||
public int getFragementShaderID(String fragmentFileName)throws SlickException{
|
||||
Integer id = shaderMap.get(fragmentFileName);
|
||||
if(id==null){
|
||||
id = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
|
||||
shaderMap.put(fragmentFileName, id);
|
||||
|
||||
GL20.glShaderSource(id, getProgramCode(fragmentFileName));
|
||||
GL20.glCompileShader(id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a shader id for a given vertex shader, generating
|
||||
* a new id if necessary.</br>
|
||||
*
|
||||
* @param vertexFileName the vertex shader to fetch an id for.
|
||||
* @returns shaderID for a given vertex shader.</br>
|
||||
*/
|
||||
public int getVertexShaderID(String vertexFileName)throws SlickException{
|
||||
Integer id = shaderMap.get(vertexFileName);
|
||||
if(id==null){
|
||||
id = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
|
||||
shaderMap.put(vertexFileName, id);
|
||||
|
||||
GL20.glShaderSource(id, getProgramCode(vertexFileName));
|
||||
GL20.glCompileShader(id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Link a shader that the shader program depends on to operate.</br>
|
||||
* @param programID
|
||||
* @param shaderID
|
||||
*/
|
||||
public void createProgramShaderDependancy(int programID,
|
||||
int shaderID){
|
||||
if(!shaderMap.containsValue(shaderID)){
|
||||
throw new IllegalArgumentException("Cannot link a shader " +
|
||||
"id that does not exist.");
|
||||
}
|
||||
|
||||
//Add Shader to list of shaders used by program
|
||||
Set<Integer> shaders = programToShaders.get(programID);
|
||||
if(shaders==null){
|
||||
shaders = new HashSet<Integer>();
|
||||
programToShaders.put(programID, shaders);
|
||||
}
|
||||
shaders.add(shaderID);
|
||||
|
||||
//Add program to list of programs used by Shader
|
||||
Set<Integer> programs = shaderToPrograms.get(shaderID);
|
||||
if(programs==null){
|
||||
programs = new HashSet<Integer>();
|
||||
shaderToPrograms.put(shaderID, programs);
|
||||
}
|
||||
programs.add(programID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void createProgramShaderDependancies(int programID,
|
||||
Iterable<Integer> ids){
|
||||
for(int id : ids){
|
||||
createProgramShaderDependancy(programID, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes the program.</br>
|
||||
* After calling this method the program specified will no longer
|
||||
* be loaded, nor will any shaders that were still in use only by
|
||||
* only that program.</br>
|
||||
*
|
||||
* @params programID the ID of the program to remove.
|
||||
*/
|
||||
//This is a rather inefficient implementation however, it need not
|
||||
//be fast and this representation is very simple to follow and
|
||||
//debug.
|
||||
public void removeProgram(int programID){
|
||||
Set<Integer> shaders = programToShaders.get(programID);
|
||||
if(shaders==null){
|
||||
throw new IllegalArgumentException("The programID " +
|
||||
programID +
|
||||
"does not exist");
|
||||
}
|
||||
|
||||
//detach Shaders from program
|
||||
for(int id : shaders){
|
||||
GL20.glDetachShader(programID, id);
|
||||
}
|
||||
|
||||
//Delete unused shaders
|
||||
for(int id : shaders){
|
||||
Set<Integer> progs = shaderToPrograms.get(id);
|
||||
progs.remove(programID);
|
||||
if(progs.isEmpty()){
|
||||
GL20.glDeleteShader(id);
|
||||
shaderToPrograms.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
//Delete Program
|
||||
GL20.glDeleteProgram(programID);
|
||||
programToShaders.remove(programID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the program code from the file <tt>filename</tt> and puts
|
||||
* it into a <tt>ByteBuffer</tt>.</br>
|
||||
* @param filename the full name of the file.
|
||||
* @return a ByteBuffer containing the program code.
|
||||
* @throws SlickException
|
||||
*/
|
||||
private ByteBuffer getProgramCode(String filename)throws SlickException{
|
||||
InputStream fileInputStream = null;
|
||||
byte[] shaderCode = null;
|
||||
|
||||
fileInputStream = ResourceLoader.getResourceAsStream(filename);
|
||||
DataInputStream dataStream = new DataInputStream(fileInputStream);
|
||||
try{
|
||||
dataStream.readFully(shaderCode = new byte[fileInputStream.available()]);
|
||||
fileInputStream.close();
|
||||
dataStream.close();
|
||||
}catch (IOException e) {
|
||||
throw new SlickException(e.getMessage());
|
||||
}
|
||||
|
||||
ByteBuffer shaderPro = BufferUtils.createByteBuffer(shaderCode.length);
|
||||
|
||||
shaderPro.put(shaderCode);
|
||||
shaderPro.flip();
|
||||
|
||||
return shaderPro;
|
||||
}
|
||||
|
||||
}
|
||||
25
src/shader/ShaderResourceManager.java
Executable file
25
src/shader/ShaderResourceManager.java
Executable file
@@ -0,0 +1,25 @@
|
||||
package shader;
|
||||
|
||||
import org.newdawn.slick.SlickException;
|
||||
|
||||
|
||||
/**
|
||||
* Simply interface for component that manages shaders source files.
|
||||
* @author Chronocide (Jeremy Klix)
|
||||
*
|
||||
*/
|
||||
public interface ShaderResourceManager{
|
||||
|
||||
int getFragementShaderID(String fragmentFileName)throws SlickException;
|
||||
|
||||
int getVertexShaderID(String vertexFileName)throws SlickException;
|
||||
|
||||
/**
|
||||
* Link a shader that the shader program depends on to operate.</br>
|
||||
* @param programID
|
||||
* @param shaderID
|
||||
*/
|
||||
void createProgramShaderDependancy(int programID, int shaderID);
|
||||
|
||||
void removeProgram(int programID);
|
||||
}
|
||||
234
src/shader/ShaderVariable.java
Executable file
234
src/shader/ShaderVariable.java
Executable file
@@ -0,0 +1,234 @@
|
||||
package shader;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
|
||||
/**
|
||||
* Class used to keep track of variables associated with this
|
||||
* shader.</br>
|
||||
*
|
||||
* @author Chronocide (Jeremy Klix)
|
||||
*
|
||||
*/
|
||||
class ShaderVariable{
|
||||
public enum Qualifier{
|
||||
ATTRIBUTE("attribute"), UNIFORM("uniform"), VARYING("varying");
|
||||
|
||||
private static final Map<String, ShaderVariable.Qualifier> stringToEnum =
|
||||
new HashMap<String, ShaderVariable.Qualifier>();
|
||||
|
||||
static{
|
||||
for(ShaderVariable.Qualifier qual : values()){
|
||||
stringToEnum.put(qual.toString(),qual);
|
||||
}
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
Qualifier(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
public static ShaderVariable.Qualifier fromString(String token){
|
||||
return stringToEnum.get(token);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type{
|
||||
BOOLEAN("boolean"), DOUBLE("double"), FLOAT("float"),
|
||||
INTEGER("integer");
|
||||
|
||||
private String name;
|
||||
|
||||
Type(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String TYPE_WARN =
|
||||
"Warning!\nProblem setting %s variable. " +
|
||||
"Expected type %s but got type %s instead.\n";
|
||||
private static final String QUAL_WARN =
|
||||
"Warning!\nProblem setting %s variable. " +
|
||||
"Expected qualifier %s but got %s instead.\n";
|
||||
|
||||
private ShaderVariable.Qualifier qualifier = null;
|
||||
private ShaderVariable.Type type = null;
|
||||
private int vecSize; //size of vector
|
||||
private int size; //size of array non arrays are size 1
|
||||
private int programID;
|
||||
|
||||
|
||||
private int location = -1;
|
||||
|
||||
/**Set true if GLSL has removed this unused variable*/
|
||||
private boolean isCulled = false;
|
||||
String name = "";
|
||||
|
||||
ShaderVariable(int programID, String name,
|
||||
ShaderVariable.Qualifier qual,
|
||||
ShaderVariable.Type type,
|
||||
int vecSize,
|
||||
int size){
|
||||
this.programID = programID;
|
||||
this.name = name;
|
||||
|
||||
this.qualifier = qual;
|
||||
this.type = type;
|
||||
if(vecSize<1){
|
||||
throw new IllegalArgumentException("size of elements must be greater than 0");
|
||||
}
|
||||
this.vecSize = vecSize;
|
||||
if(size<1){
|
||||
throw new IllegalArgumentException("number of elements must be greater than 0");
|
||||
}
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean equals(Object obj){
|
||||
if(obj instanceof ShaderVariable){
|
||||
return this.toString().equals(obj.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int hashCode(){
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
|
||||
//TODO TEST
|
||||
void setUniformValue(boolean[] vals){
|
||||
if(this.type!=Type.BOOLEAN){
|
||||
System.err.printf(TYPE_WARN, this.name, this.type, Type.BOOLEAN);
|
||||
}
|
||||
if(this.qualifier!=Qualifier.UNIFORM){
|
||||
System.err.printf(QUAL_WARN, this.name, this.qualifier, Qualifier.UNIFORM);
|
||||
}
|
||||
if(vals.length!=vecSize){
|
||||
throw new AssertionError("Incorrect number of arguments.");
|
||||
}
|
||||
|
||||
//No GL methods to set boolean uniforms exist
|
||||
if(location==-1){
|
||||
CharSequence param = new StringBuffer(name);
|
||||
location = GL20.glGetUniformLocation(programID, param);
|
||||
locationCheck();
|
||||
}
|
||||
|
||||
IntBuffer fb = BufferUtils.createIntBuffer(vals.length);
|
||||
for(boolean b : vals){
|
||||
fb.put(b? 1 : 0);
|
||||
}
|
||||
fb.flip();
|
||||
switch(vecSize){
|
||||
case 1: GL20.glUniform1(location, fb); break;
|
||||
case 2: GL20.glUniform2(location, fb); break;
|
||||
case 3: GL20.glUniform3(location, fb); break;
|
||||
case 4: GL20.glUniform4(location, fb); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setUniformValue(float[] vals){
|
||||
if(this.type!=Type.FLOAT){
|
||||
System.err.printf(TYPE_WARN, this.name, this.type, Type.FLOAT);
|
||||
}
|
||||
if(this.qualifier!=Qualifier.UNIFORM){
|
||||
System.err.printf(QUAL_WARN, this.name, this.qualifier, Qualifier.UNIFORM);
|
||||
}
|
||||
if(vals.length!=vecSize*size){
|
||||
throw new AssertionError("Incorrect number of values.\n" +
|
||||
"Expected " + vecSize*size + " vlaues but got " +
|
||||
vals.length + " values instead.");
|
||||
}
|
||||
|
||||
if(location==-1){
|
||||
CharSequence param = new StringBuffer(name);
|
||||
location = GL20.glGetUniformLocation(programID, param);
|
||||
locationCheck();
|
||||
}
|
||||
|
||||
FloatBuffer fb = BufferUtils.createFloatBuffer(vals.length);
|
||||
fb.put(vals);
|
||||
fb.flip();
|
||||
switch(vecSize){
|
||||
case 1: GL20.glUniform1(location, fb); break;
|
||||
case 2: GL20.glUniform2(location, fb); break;
|
||||
case 3: GL20.glUniform3(location, fb); break;
|
||||
case 4: GL20.glUniform4(location, fb); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setUniformValue(int[] vals){
|
||||
if(this.type!=Type.INTEGER){
|
||||
System.err.printf(TYPE_WARN, this.type, Type.INTEGER);
|
||||
}
|
||||
if(this.qualifier!=Qualifier.UNIFORM){
|
||||
System.err.printf(QUAL_WARN, this.name, this.qualifier, Qualifier.UNIFORM);
|
||||
}
|
||||
if(vals.length!=vecSize*size){
|
||||
throw new AssertionError("Incorrect number of values.\n" +
|
||||
"Expected " + vecSize*size + " vlaues but got " +
|
||||
vals.length + " values instead.");
|
||||
}
|
||||
|
||||
if(location==-1){
|
||||
CharSequence param = new StringBuffer(name);
|
||||
location = GL20.glGetUniformLocation(programID, param);
|
||||
locationCheck();
|
||||
}
|
||||
|
||||
IntBuffer fb = BufferUtils.createIntBuffer(vals.length);
|
||||
fb.put(vals);
|
||||
fb.flip();
|
||||
switch(vecSize){
|
||||
case 1: GL20.glUniform1(location, fb); break;
|
||||
case 2: GL20.glUniform2(location, fb); break;
|
||||
case 3: GL20.glUniform3(location, fb); break;
|
||||
case 4: GL20.glUniform4(location, fb); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void locationCheck(){
|
||||
if(location==-1 && !isCulled){
|
||||
System.err.println("Location for variable " + name +
|
||||
"could not be found.\nGLSL may remove " +
|
||||
"any vairable that does not contribute " +
|
||||
"to an output. Check and ensure " +
|
||||
"that " + name + "is an active variable.\n");
|
||||
isCulled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user