/* * Created on 26. August 2006, 15:26 */ package com.mbien.engine.glsl; import com.mbien.engine.util.GLRunnable; import com.mbien.engine.util.GLWorker; import com.mbien.engine.util.GLWorkerImpl; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.EnumMap; import java.util.logging.Logger; import javax.media.opengl.GL; import javax.media.opengl.GL2GL3; import javax.media.opengl.GLContext; /** * @author Michael Bien */ public class GLSLShader { public final TYPE type; private final String source; final String[] shaderNames; private int handle; private String compilerMsg; private final static EnumMap<TYPE, Boolean> SUPPORTED_SHADER = new EnumMap<TYPE, Boolean>(TYPE.class); private boolean throwExceptionOnCompilerWarning = false; private CodeFragment[] fragments; public GLSLShader(String... filePath) { this(toFiles(filePath)); } public GLSLShader(File... files) { this.type = TYPE.parse(files[0].getName()); if(type==null) throw new IllegalArgumentException("Wrong file ending; only .[gl(sl)]frag, .[gl(sl)]vert and .[gl(sl)]geom endings are allowed"); shaderNames = new String[files.length]; source = readSourceFile(files); } public GLSLShader(TYPE type, File... files) { if(type == null) throw new IllegalArgumentException("null as shader type not allowed"); this.type = type; shaderNames = new String[files.length]; source = readSourceFile(files); } public GLSLShader(TYPE type, String source, String name) { if(type == null) throw new IllegalArgumentException("null as shader type not allowed"); this.type = type; shaderNames = new String[]{ name }; this.source = source; } public GLSLShader(TYPE type, String[] sources, String[] names) { if(type == null) throw new IllegalArgumentException("null as shader type not allowed"); this.type = type; StringBuilder sb = new StringBuilder(); for (int i = 0; i < sources.length; i++) { sb.append(sources[i]); } shaderNames = names; source = sb.toString(); } public GLSLShader(TYPE type, String filepath, ShaderSourceLoader provider) { if(type == null) throw new IllegalArgumentException("null as shader type not allowed"); fragments = provider.loadWithDependencies(filepath); this.type = type; this.shaderNames = new String[fragments.length]; StringBuilder sb = new StringBuilder(); for (int i = 0; i < fragments.length; i++) { shaderNames[i] = fragments[i].name; sb.append(fragments[i].source).append("\n"); } this.source = sb.toString(); } public GLSLShader(TYPE type, CodeFragment mainFragment, String path, ShaderSourceLoader provider) { if(type == null) throw new IllegalArgumentException("null as shader type not allowed"); fragments = provider.loadWithDependencies(mainFragment, path); this.type = type; this.shaderNames = new String[fragments.length]; StringBuilder sb = new StringBuilder(); for (int i = 0; i < fragments.length; i++) { shaderNames[i] = fragments[i].name; sb.append(fragments[i].source).append("\n"); } this.source = sb.toString(); } // public GLSLShader(File file, boolean importDependencies) { // // if(type == null) // throw new IllegalArgumentException("null as shader type not allowed"); // // this.type = type; // shaderNames = new String[]{ name }; // this.source = source; // } private static File[] toFiles(String... names) { File[] files = new File[names.length]; for(int i = 0; i < files.length; i++) files[i] = new File(names[i]); return files; } private final String readSourceFile(File... files) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < files.length; i ++) { FileReader reader = null; int length = 0; File sourceFile = files[i]; char[] buffer = new char[(int)sourceFile.length()]; shaderNames[i] = sourceFile.getName(); try { reader = new FileReader(sourceFile); length = reader.read(buffer); } catch (FileNotFoundException ex) { getLog().severe("shader source not found\n"+ex.getMessage()); } catch (IOException ex) { getLog().severe("exception while reading shader source\n"+ex.getMessage()); }finally { if(reader != null) { try{ reader.close(); }catch(IOException e){ getLog().severe("can't close opened stream\n"+e.getMessage()); } } } sb.append(buffer, 0, length); } getLog().fine("shader source read done"); return sb.toString(); } public void initShader(GL2GL3 gl) throws GLSLCompileException { handle = gl.glCreateShader(type.GL_TYPE); gl.glShaderSource(handle, 1, new String[] {source}, new int[] {source.length()}, 0); gl.glCompileShader(handle); checkShader(gl); } public void deleteShader(GL2GL3 gl) { gl.glDeleteShader(handle); } private void checkShader(GL2GL3 gl) throws GLSLCompileException { boolean error = false; // check compile state int[] buffer = new int[1]; gl.glGetShaderiv(handle, GL2GL3.GL_COMPILE_STATUS, buffer, 0); if(buffer[0] == GL.GL_FALSE) { // getLog().warning("error compiling shader:\n"+getName()); error = true; } // log info log gl.glGetShaderiv(handle, GL2GL3.GL_INFO_LOG_LENGTH, buffer, 0); if(buffer[0] > 0) { byte[] log = new byte[buffer[0]]; gl.glGetShaderInfoLog(handle, buffer[0], buffer, 0, log, 0); compilerMsg = new String(log, 0, log.length-1); if(error || throwExceptionOnCompilerWarning && compilerMsg.contains("WARNING")) { throw new GLSLCompileException(this, compilerMsg.split("\n")); } } } public static Logger getLog() { return Logger.getLogger(GLSLShader.class.getPackage().getName()); } public int getID() { return handle; } public void setThrowExceptionOnCompilerWarning(boolean b) { this.throwExceptionOnCompilerWarning = b; } public String getName() { StringBuilder sb = new StringBuilder(); for(int i = 0; i < shaderNames.length; i++) { sb.append(shaderNames[i]); if(i+1 < shaderNames.length) sb.append(", "); } return sb.toString(); } public String getCompilerMsg() { return compilerMsg; } public CodeFragment[] getFragments() { return fragments; } private static boolean isShaderSupported(TYPE type) { if(SUPPORTED_SHADER.get(type) == null) { GLWorker worker = GLWorkerImpl.getDefault(); worker.work(new GLRunnable() { public void run(GLContext context) { GL gl = context.getGL(); SUPPORTED_SHADER.put(TYPE.VERTEX, gl.isExtensionAvailable(TYPE.VERTEX.EXT_STRING)); SUPPORTED_SHADER.put(TYPE.FRAGMENT, gl.isExtensionAvailable(TYPE.FRAGMENT.EXT_STRING)); SUPPORTED_SHADER.put(TYPE.GEOMETRY, gl.isExtensionAvailable(TYPE.GEOMETRY.EXT_STRING)); } }); } return SUPPORTED_SHADER.get(type); } public enum TYPE { VERTEX(GL2GL3.GL_VERTEX_SHADER, "GL_ARB_vertex_shader"), FRAGMENT(GL2GL3.GL_FRAGMENT_SHADER, "GL_ARB_fragment_shader"), GEOMETRY(GL2GL3.GL_GEOMETRY_SHADER, "GL_EXT_geometry_shader4"); public final int GL_TYPE; public final String EXT_STRING; private TYPE(int gltype, String extention) { GL_TYPE = gltype; EXT_STRING = extention; } public static TYPE parse(String ext) { String _ext="."+ext; if(_ext.endsWith(".frag") || _ext.endsWith(".glfrag") || _ext.endsWith(".glslfrag") || _ext.endsWith(".gl-frag") || _ext.endsWith(".glsl-frag")) return TYPE.FRAGMENT; if(_ext.endsWith(".vert") || _ext.endsWith(".glvert") || _ext.endsWith(".glslvert") || _ext.endsWith(".gl-vert") || _ext.endsWith(".glsl-vert")) return TYPE.VERTEX; if(_ext.endsWith(".geom") || _ext.endsWith(".glgeom") || _ext.endsWith(".glslgeom") || _ext.endsWith(".gl-geom") || _ext.endsWith(".glsl-geom")) return TYPE.GEOMETRY; return null; } public static TYPE fromMime(String string) { if(string.equalsIgnoreCase("text/x-glsl-fragment-shader")) return TYPE.FRAGMENT; else if(string.equalsIgnoreCase("text/x-glsl-vertex-shader")) return TYPE.VERTEX; else if(string.equalsIgnoreCase("text/x-glsl-geometry-shader")) return TYPE.GEOMETRY; return null; } public boolean isSupported() { return isShaderSupported(this); } } }