package electrosphere.renderer.shader; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.management.RuntimeErrorException; import org.lwjgl.opengl.GL40; import electrosphere.engine.Globals; import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.RenderingEngine; import electrosphere.util.FileUtils; /** * A visual shader program */ public class VisualShader implements Shader { /** * The vertex shader location */ int vertexShader; /** * The fragment shader location */ int fragmentShader; /** * The shader's ID */ int shaderId; /** * The map of uniform location -> current value of uniform */ public Map uniformMap = new HashMap(); /** * The map of path -> already compiled shader */ static Map alreadyCompiledMap = new HashMap(); /** * Recursively preprocesses a file * @param currentFile The file to preprocess * @return The contents of the file */ private static String recursivelyPreprocessFile(String input){ return VisualShader.recursivelyPreprocessFile(FileUtils.getAssetFile(input), new LinkedList()); } /** * Recursively preprocesses a file * @param currentFile The file to preprocess * @return The contents of the file */ private static String recursivelyPreprocessFile(File currentFile, List includes){ String contents = null; try { contents = Files.readString(currentFile.toPath()); } catch (IOException e) { LoggerInterface.loggerRenderer.ERROR(e); } Pattern includePattern = Pattern.compile("#include \"(.*)\""); Matcher matcher = includePattern.matcher(contents); int i = 1; while(matcher.find()){ String group = matcher.group(i); if(!includes.contains(group)){ File directory = currentFile.getParentFile(); File newFile = new File(directory.getPath() + "/" + group); String includeContent = VisualShader.recursivelyPreprocessFile(newFile, includes); contents = contents.replace("#include \"" + group + "\"", includeContent); } i++; } //remove strings that we don't want to include contents = contents.replace("#extension GL_ARB_shading_language_include : require",""); return contents; } /** * Smart assembles a shader * @param ContainsBones true if the mesh contains bones * @param apply_lighting true if lighting should be applied * @return The visual shader */ public static VisualShader smartAssembleShader(boolean ContainsBones, boolean apply_lighting){ //return shader if it has already been compiled String shaderKey = ContainsBones + "-" + apply_lighting; if(alreadyCompiledMap.containsKey(shaderKey)){ return alreadyCompiledMap.get(shaderKey); } String vertex_shader_path = ""; if(ContainsBones){ vertex_shader_path = "/Shaders/VertexShader.vs"; } else { vertex_shader_path = "/Shaders/VertexShaderNoBones.vs"; } String fragment_shader_path = "/Shaders/FragmentShader.fs"; // //Create ShaderProgram object // VisualShader rVal = new VisualShader(); // //Read in shader programs // String vertexShaderSource = VisualShader.recursivelyPreprocessFile(vertex_shader_path); String fragmentShaderSource = VisualShader.recursivelyPreprocessFile(fragment_shader_path); //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.vertexShader = GL40.glCreateShader(GL40.GL_VERTEX_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.vertexShader, vertexShaderSource); //Compiles the source for the vertex shader object GL40.glCompileShader(rVal.vertexShader); //The following tests if the vertex shader compiles successfully int success; success = GL40.glGetShaderi(rVal.vertexShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.vertexShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.vertexShader))); } //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader rVal.fragmentShader = GL40.glCreateShader(GL40.GL_FRAGMENT_SHADER); //This points the opengl shadder object to its proper source GL40.glShaderSource(rVal.fragmentShader, fragmentShaderSource); //This compiles the shader object GL40.glCompileShader(rVal.fragmentShader); //This tests for the success of the compile attempt success = GL40.glGetShaderi(rVal.fragmentShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.fragmentShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.fragmentShader))); } //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram rVal.shaderId = GL40.glCreateProgram(); //This attaches the vertex and fragment shaders to the program GL40.glAttachShader(rVal.shaderId, rVal.vertexShader); GL40.glAttachShader(rVal.shaderId, rVal.fragmentShader); //This links the program to the GPU (I think its to the GPU anyway) GL40.glLinkProgram(rVal.shaderId); //Tests for the success of the shader program creation success = GL40.glGetProgrami(rVal.shaderId, GL40.GL_LINK_STATUS); if (success != GL40.GL_TRUE) { throw new RuntimeException(GL40.glGetProgramInfoLog(rVal.shaderId)); } //Deletes the individual shader objects to free up memory GL40.glDeleteShader(rVal.vertexShader); GL40.glDeleteShader(rVal.fragmentShader); alreadyCompiledMap.put(shaderKey,rVal); return rVal; } /** * Intelligently assembles a shader for use in OIT part of render pipeline * @param ContainsBones True if contains bones * @param apply_lighting True if should apply lighting * @return The int-pointer to the shader compiled */ public static VisualShader smartAssembleOITProgram(boolean ContainsBones, boolean apply_lighting){ //return shader if it has already been compiled String shaderKey = "oit" + ContainsBones + "-" + apply_lighting; if(alreadyCompiledMap.containsKey(shaderKey)){ return alreadyCompiledMap.get(shaderKey); } String vertex_shader_path = ""; if(ContainsBones){ vertex_shader_path = "/Shaders/core/oit/general/VertexShader.vs"; } else { vertex_shader_path = "/Shaders/core/oit/general/VertexShaderNoBones.vs"; } String fragment_shader_path = "/Shaders/core/oit/general/FragmentShader.fs"; // //Create ShaderProgram object // VisualShader rVal = new VisualShader(); // //Read in shader programs // String vertexShaderSource = VisualShader.recursivelyPreprocessFile(vertex_shader_path); String fragmentShaderSource = VisualShader.recursivelyPreprocessFile(fragment_shader_path); //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.vertexShader = GL40.glCreateShader(GL40.GL_VERTEX_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.vertexShader, vertexShaderSource); //Compiles the source for the vertex shader object GL40. glCompileShader(rVal.vertexShader); //The following tests if the vertex shader compiles successfully int success; success = GL40.glGetShaderi(rVal.vertexShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.vertexShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.vertexShader))); } //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader rVal.fragmentShader = GL40.glCreateShader(GL40.GL_FRAGMENT_SHADER); //This points the opengl shadder object to its proper source GL40.glShaderSource(rVal.fragmentShader, fragmentShaderSource); //This compiles the shader object GL40.glCompileShader(rVal.fragmentShader); //This tests for the success of the compile attempt success = GL40.glGetShaderi(rVal.fragmentShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.fragmentShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.fragmentShader))); } //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram rVal.shaderId = GL40.glCreateProgram(); //This attaches the vertex and fragment shaders to the program GL40.glAttachShader(rVal.shaderId, rVal.vertexShader); GL40.glAttachShader(rVal.shaderId, rVal.fragmentShader); //This links the program to the GPU (I think its to the GPU anyway) GL40. glLinkProgram(rVal.shaderId); //Tests for the success of the shader program creation success = GL40.glGetProgrami(rVal.shaderId, GL40.GL_LINK_STATUS); if (success != GL40.GL_TRUE) { throw new RuntimeException(GL40.glGetProgramInfoLog(rVal.shaderId)); } //Deletes the individual shader objects to free up memory GL40.glDeleteShader(rVal.vertexShader); GL40.glDeleteShader(rVal.fragmentShader); alreadyCompiledMap.put(shaderKey,rVal); return rVal; } /** * Loads the default shader program * @return The default shader */ public static VisualShader loadDefaultShaderProgram(){ // //Create ShaderProgram object // VisualShader rVal = new VisualShader(); // //Read in shader programs // String vertexShaderSource = VisualShader.recursivelyPreprocessFile(AssetDataStrings.SHADER_DEFAULT_VERT); String fragmentShaderSource = VisualShader.recursivelyPreprocessFile(AssetDataStrings.SHADER_DEFAULT_FRAG); //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.vertexShader = GL40.glCreateShader(GL40.GL_VERTEX_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.vertexShader, vertexShaderSource); //Compiles the source for the vertex shader object GL40.glCompileShader(rVal.vertexShader); //The following tests if the vertex shader compiles successfully int success; success = GL40.glGetShaderi(rVal.vertexShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.vertexShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.vertexShader))); } //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader rVal.fragmentShader = GL40.glCreateShader(GL40.GL_FRAGMENT_SHADER); //This points the opengl shadder object to its proper source GL40.glShaderSource(rVal.fragmentShader, fragmentShaderSource); //This compiles the shader object GL40.glCompileShader(rVal.fragmentShader); //This tests for the success of the compile attempt success = GL40.glGetShaderi(rVal.fragmentShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!"); LoggerInterface.loggerRenderer.WARNING("Source is: "); LoggerInterface.loggerRenderer.WARNING(GL40.glGetShaderSource(rVal.fragmentShader)); LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL40.glGetShaderInfoLog(rVal.fragmentShader))); } //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram rVal.shaderId = GL40.glCreateProgram(); //This attaches the vertex and fragment shaders to the program GL40.glAttachShader(rVal.shaderId, rVal.vertexShader); GL40.glAttachShader(rVal.shaderId, rVal.fragmentShader); //This links the program to the GPU (I think its to the GPU anyway) GL40.glLinkProgram(rVal.shaderId); //Tests for the success of the shader program creation success = GL40.glGetProgrami(rVal.shaderId, GL40.GL_LINK_STATUS); if (success != GL40.GL_TRUE) { throw new RuntimeException(GL40.glGetProgramInfoLog(rVal.shaderId)); } //Deletes the individual shader objects to free up memory GL40.glDeleteShader(rVal.vertexShader); GL40.glDeleteShader(rVal.fragmentShader); return rVal; } /** * Loads a specific shader * @param vertexPath The vertex shader's path * @param fragmentPath The fragment shader's path * @return The visual shader */ public static VisualShader loadSpecificShader(String vertexPath, String fragmentPath){ VisualShader rVal = new VisualShader(); // //Read in shader programs // String vertexShaderSource = VisualShader.recursivelyPreprocessFile(vertexPath); String fragmentShaderSource = VisualShader.recursivelyPreprocessFile(fragmentPath); //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.vertexShader = GL40.glCreateShader(GL40.GL_VERTEX_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.vertexShader, vertexShaderSource); //Compiles the source for the vertex shader object GL40.glCompileShader(rVal.vertexShader); //The following tests if the vertex shader compiles successfully int success; success = GL40.glGetShaderi(rVal.vertexShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { List errorLines = new LinkedList(); LoggerInterface.loggerRenderer.WARNING("Failed to load " + vertexPath + " ... attempting alternatives"); //report failed to load shader errorLines.add("Vertex Shader failed to compile!"); errorLines.add("Source File is: " + vertexPath); errorLines.add("Source is: "); errorLines.add(GL40.glGetShaderSource(rVal.vertexShader)); errorLines.add(new RuntimeException(GL40.glGetShaderInfoLog(rVal.vertexShader))); //attempt loading alternative shaders List availableAlternatives = Globals.shaderOptionMap.getAlternativesForFile(vertexPath); int alternativesAttempted = 0; if(availableAlternatives != null){ for(String alternative : availableAlternatives){ alternativesAttempted++; //load file try { vertexShaderSource = FileUtils.getAssetFileAsString(alternative); } catch (IOException e) { LoggerInterface.loggerEngine.ERROR("Failed to load shader alternative " + alternative, e); } //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.vertexShader = GL40.glCreateShader(GL40.GL_VERTEX_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.vertexShader, vertexShaderSource); //Compiles the source for the vertex shader object GL40.glCompileShader(rVal.vertexShader); //The following tests if the vertex shader compiles successfully success = GL40.glGetShaderi(rVal.vertexShader, GL40.GL_COMPILE_STATUS); if (success == GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Successfully loaded alternative shader " + alternative); break; } else { errorLines.add("Vertex Shader failed to compile!"); errorLines.add("Source File is: " + vertexPath); errorLines.add("Source is: "); errorLines.add(GL40.glGetShaderSource(rVal.vertexShader)); errorLines.add(new RuntimeException(GL40.glGetShaderInfoLog(rVal.vertexShader))); } } if(success != GL40.GL_TRUE){ for(Object object : errorLines){ if(object instanceof String){ LoggerInterface.loggerRenderer.WARNING((String)object); } else if(object instanceof RuntimeErrorException){ LoggerInterface.loggerRenderer.ERROR("Runtime Exception", (RuntimeErrorException)object); } } } LoggerInterface.loggerRenderer.WARNING("Attempted " + alternativesAttempted + " alternative shaders"); } } //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader rVal.fragmentShader = GL40.glCreateShader(GL40.GL_FRAGMENT_SHADER); //This points the opengl shadder object to its proper source GL40.glShaderSource(rVal.fragmentShader, fragmentShaderSource); //This compiles the shader object GL40.glCompileShader(rVal.fragmentShader); //This tests for the success of the compile attempt success = GL40.glGetShaderi(rVal.fragmentShader, GL40.GL_COMPILE_STATUS); if (success != GL40.GL_TRUE) { List errorLines = new LinkedList(); LoggerInterface.loggerRenderer.WARNING("Failed to load " + fragmentPath + " ... attempting alternatives"); //report failed to load shader errorLines.add("Fragment Shader failed to compile!"); errorLines.add("Source File is: " + fragmentPath); errorLines.add("Source is: "); errorLines.add(GL40.glGetShaderSource(rVal.fragmentShader)); errorLines.add(new RuntimeException(GL40.glGetShaderInfoLog(rVal.fragmentShader))); //attempt loading alternative shaders List availableAlternatives = Globals.shaderOptionMap.getAlternativesForFile(fragmentPath); int alternativesAttempted = 0; if(availableAlternatives != null){ for(String alternative : availableAlternatives){ alternativesAttempted++; //load file try { fragmentShaderSource = FileUtils.getAssetFileAsString(alternative); } catch (IOException e) { LoggerInterface.loggerEngine.ERROR("Failed to load shader alternative " + alternative, e); } //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader" rVal.fragmentShader = GL40.glCreateShader(GL40.GL_FRAGMENT_SHADER); //This alerts openGL to the presence of a vertex shader and points the shader at its source GL40.glShaderSource(rVal.fragmentShader, fragmentShaderSource); //Compiles the source for the vertex shader object GL40.glCompileShader(rVal.fragmentShader); //The following tests if the vertex shader compiles successfully success = GL40.glGetShaderi(rVal.fragmentShader, GL40.GL_COMPILE_STATUS); if (success == GL40.GL_TRUE) { LoggerInterface.loggerRenderer.WARNING("Successfully loaded alternative shader " + alternative); break; } else { errorLines.add("Fragment Shader failed to compile!"); errorLines.add("Source File is: " + fragmentPath); errorLines.add("Source is: "); errorLines.add(GL40.glGetShaderSource(rVal.fragmentShader)); errorLines.add(new RuntimeException(GL40.glGetShaderInfoLog(rVal.fragmentShader))); } } if(success != GL40.GL_TRUE){ for(Object object : errorLines){ if(object instanceof String){ LoggerInterface.loggerRenderer.WARNING((String)object); } else if(object instanceof RuntimeErrorException){ LoggerInterface.loggerRenderer.ERROR("Runtime Exception", (RuntimeErrorException)object); } } } LoggerInterface.loggerRenderer.WARNING("Attempted " + alternativesAttempted + " alternative shaders"); } } //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram rVal.shaderId = GL40.glCreateProgram(); //This attaches the vertex and fragment shaders to the program GL40.glAttachShader(rVal.shaderId, rVal.vertexShader); GL40.glAttachShader(rVal.shaderId, rVal.fragmentShader); //This links the program to the GPU (I think its to the GPU anyway) GL40.glLinkProgram(rVal.shaderId); //Tests for the success of the shader program creation success = GL40.glGetProgrami(rVal.shaderId, GL40.GL_LINK_STATUS); if (success != GL40.GL_TRUE) { LoggerInterface.loggerRenderer.ERROR(GL40.glGetProgramInfoLog(rVal.shaderId), new RuntimeException(GL40.glGetProgramInfoLog(rVal.shaderId))); LoggerInterface.loggerRenderer.WARNING("Shader sources: " + vertexPath + " " + fragmentPath); return Globals.defaultMeshShader; // throw new RuntimeException(glGetProgramInfoLog(rVal.shaderProgram)); } //Deletes the individual shader objects to free up memory GL40.glDeleteShader(rVal.vertexShader); GL40.glDeleteShader(rVal.fragmentShader); Globals.renderingEngine.checkError(); return rVal; } /** * Tries to set a uniform * @param uniformName The name of the uniform * @param value The value to set the uniform to */ public void setUniform(OpenGLState openGLState, String uniformName, Object value){ // //Error checking if(uniformName == null || uniformName.equals("")){ throw new IllegalArgumentException("Trying to set invalid uniform name"); } if(this.getId() != openGLState.getActiveShader().getId()){ throw new IllegalStateException("Trying to set uniform on shader that is not active"); } // //get uniform location int uniformLocation = GL40.glGetUniformLocation(this.getId(), uniformName); int glErrorCode = Globals.renderingEngine.getError(); if(glErrorCode != 0){ LoggerInterface.loggerRenderer.DEBUG_LOOP(RenderingEngine.getErrorInEnglish(glErrorCode)); LoggerInterface.loggerRenderer.DEBUG_LOOP("Shader id: " + this.getId()); } // //set the uniform if(uniformLocation == INVALID_UNIFORM_NAME){ LoggerInterface.loggerRenderer.DEBUG_LOOP("Searched for uniform in a shader that does not contain it. Uniform name: \"" + uniformName + "\""); } else { ShaderUtils.setUniform(openGLState, this.uniformMap, uniformLocation, value); } } /** * Gets the location of a given uniform * @param uniformName The name of the uniform * @return The location of the uniform */ public int getUniformLocation(String uniformName){ return GL40.glGetUniformLocation(this.getId(), uniformName); } @Override public int getId() { return this.shaderId; } }