package me.hammerle.snuviengine.api; import java.io.File; import java.io.IOException; import java.nio.FloatBuffer; import java.nio.file.Files; import java.util.LinkedList; import java.util.List; import org.lwjgl.BufferUtils; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL14.*; import static org.lwjgl.opengl.GL20.*; public final class Shader { private static int program = -1; private static int width = 1024; private static int height = 620; private static int scale = 2; private static final List TASKS = new LinkedList<>(); protected static boolean initDone = false; private final static FontRenderer FONT_RENDERER = new FontRenderer(); private final static ColorRenderer COLOR_RENDERER = new ColorRenderer(); // uniform stuff private static int unifViewMatrix = -1; private static int unifModelMatrix = -1; private static MatrixStack modelMatrix; private static int unifAmbientLight = -1; private static int[][] unifLight; private static int unifUseTexture = -1; private static int unifUseColor = -1; private static int unifUseLight = -1; protected static void init() { program = createShaderProgram("shaders/vertex.vs", "shaders/fragment.fs"); glUseProgram(program); unifViewMatrix = glGetUniformLocation(program, "viewMatrix"); updateViewMatrix(); unifModelMatrix = glGetUniformLocation(program, "modelMatrix"); modelMatrix = new MatrixStack(20); updateMatrix(); unifAmbientLight = glGetUniformLocation(program, "ambientLight"); setAmbientLight(1.0f, 1.0f, 1.0f); unifLight = new int[32][3]; for(int index = 0; index < unifLight.length; index++) { unifLight[index][0] = glGetUniformLocation(program, "lights[" + index + "].color"); unifLight[index][1] = glGetUniformLocation(program, "lights[" + index + "].pos"); unifLight[index][2] = glGetUniformLocation(program, "lights[" + index + "].strength"); setLightColor(index, 0.0f, 0.0f, 0.0f); setLightLocation(index, 0.0f, 0.0f); setLightStrength(index, 0.0f); } unifUseTexture = glGetUniformLocation(program, "useTexture"); setTextureEnabled(false); unifUseColor = glGetUniformLocation(program, "useColor"); setColorEnabled(false); unifUseLight = glGetUniformLocation(program, "useLight"); setLightEnabled(false); setViewPort(width, height); initDone = true; } protected static void addTask(Runnable r) { TASKS.add(r); } protected static void doTasks() { if(!TASKS.isEmpty()) { TASKS.forEach(r -> r.run()); TASKS.clear(); } } public static FontRenderer getFontRenderer() { return FONT_RENDERER; } public static ColorRenderer getColorRenderer() { return COLOR_RENDERER; } private static void updateViewMatrix() { FloatBuffer buffer = BufferUtils.createFloatBuffer(16); buffer.put(2.0f / width); buffer.put(0.0f); buffer.put(0.0f); buffer.put(0.0f); buffer.put(0.0f); buffer.put(-2.0f / height); buffer.put(0.0f); buffer.put(0.0f); buffer.put(0.0f); buffer.put(0.0f); buffer.put(-1.0f / height); buffer.put(0.0f); buffer.put(-1.0f); buffer.put(1.0f); buffer.put(0.5f); buffer.put(1.0f); buffer.flip(); glUniformMatrix4fv(unifViewMatrix, false, buffer); } protected static void setViewPort(int width, int height) { scale = 1; while(width / (scale + 1) >= 400 && height / (scale + 1) >= 300) { scale++; } Shader.width = width / scale; Shader.height = height / scale; updateViewMatrix(); } public static int getViewScale() { return scale; } public static int getViewWidth() { return width; } public static int getViewHeight() { return height; } public static void updateMatrix() { glUniformMatrix4fv(unifModelMatrix, false, modelMatrix.getData()); } public static void pushMatrix() { modelMatrix.push(); } public static void popMatrix() { modelMatrix.pop(); } public static void translate(float tx, float ty) { modelMatrix.translate(tx, ty); } public static void translateTo(float tx, float ty) { modelMatrix.translateTo(tx, ty); } public static void scale(float sx, float sy) { modelMatrix.scale(sx, sy); } public static void rotate(float angle) { modelMatrix.rotate(angle); } public static void setAmbientLight(float r, float g, float b) { glUniform3f(unifAmbientLight, r, g, b); } private static void checkLightIndex(int index) { if(index < 0 || index > unifLight.length) { throw new ShaderException("'" + index + "' is not a valid light index"); } } public static void setLightColor(int index, float r, float g, float b) { checkLightIndex(index); glUniform3f(unifLight[index][0], r, g, b); } public static void setLightLocation(int index, float x, float y) { checkLightIndex(index); glUniform2f(unifLight[index][1], x, y); } public static void setLightStrength(int index, float strength) { checkLightIndex(index); glUniform1f(unifLight[index][2], strength); } public static void setTextureEnabled(boolean use) { glUniform1i(unifUseTexture, use ? 1 : 0); } public static void setColorEnabled(boolean use) { glUniform1i(unifUseColor, use ? 1 : 0); } public static void setLightEnabled(boolean use) { glUniform1i(unifUseLight, use ? 1 : 0); } public static void setDepthTestEnabled(boolean use) { if(use) { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); } else { glDisable(GL_DEPTH_TEST); } } public static void setBlendingEnabled(boolean use) { if(use) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); } else { glDisable(GL_BLEND); } } // ------------------------------------------------------------------------- // general stuff // ------------------------------------------------------------------------- private static String[] readFile(String path) { try { File f = new File(path); List list = Files.readAllLines(f.toPath()); list.replaceAll(s -> s + "\n"); return list.toArray(new String[list.size()]); } catch(IOException ex) { throw new ShaderException("failed reading shader file '" + path + "'"); } } private static String getError() { StringBuilder sb = new StringBuilder(); int glErr = glGetError(); while(glErr != GL_NO_ERROR) { sb.append(glErr); sb.append(" "); glErr = glGetError(); } return sb.toString(); } private static int createShaderProgram(String vertex, String fragment) { // --------------------------------------------------------------------- // vertex shader // --------------------------------------------------------------------- String vShaderSource[] = readFile(vertex); int vShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vShader, vShaderSource); glCompileShader(vShader); String error = getError(); if(!error.isEmpty()) { throw new ShaderException("failed compiling vertex shader '" + vertex + "' " + error); } int compiled = glGetShaderi(vShader, GL_COMPILE_STATUS); if(compiled != 1) { throw new ShaderException("failed compiling vertex shader '" + vertex + "' " + compiled + " " + glGetShaderInfoLog(vShader)); } // --------------------------------------------------------------------- // fragment shader // --------------------------------------------------------------------- String fShaderSource[] = readFile(fragment); int fShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fShader, fShaderSource); glCompileShader(fShader); error = getError(); if(!error.isEmpty()) { throw new ShaderException("failed compiling fragment shader '" + fragment + "' " + error); } compiled = glGetShaderi(fShader, GL_COMPILE_STATUS); if(compiled != 1) { throw new ShaderException("failed compiling fragment shader '" + fragment + "' " + compiled + " " + glGetShaderInfoLog(fShader)); } // --------------------------------------------------------------------- // linking // --------------------------------------------------------------------- int vfprogram = glCreateProgram(); glAttachShader(vfprogram, vShader); glAttachShader(vfprogram, fShader); glLinkProgram(vfprogram); error = getError(); if(!error.isEmpty()) { throw new ShaderException("failed linking shaders '" + vertex + "' and '" + fragment + "' " + error); } compiled = glGetProgrami(vfprogram, GL_LINK_STATUS); if(compiled != 1) { throw new ShaderException("failed linking shaders '" + vertex + "' and '" + fragment + "' " + compiled + " " + glGetProgramInfoLog(vfprogram)); } glDeleteShader(vShader); glDeleteShader(fShader); return vfprogram; } }