/** * Copyright 2010 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.opengl.util.glsl; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.StringReader; import java.net.URLConnection; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Arrays; import java.util.Iterator; import java.util.Set; import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL3; import javax.media.opengl.GLES2; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; import jogamp.opengl.Debug; import com.jogamp.common.nio.Buffers; import com.jogamp.common.util.IOUtil; import com.jogamp.common.util.VersionNumber; /** * Convenient shader code class to use and instantiate vertex or fragment programs. *

* A documented example of how to use this code is available * {@link #create(GL2ES2, int, Class, String, String, String, boolean) here} and * {@link #create(GL2ES2, int, int, Class, String, String[], String, String) here}. *

*/ public class ShaderCode { public static final boolean DEBUG_CODE = Debug.isPropertyDefined("jogl.debug.GLSLCode", true); /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in source code: vp */ public static final String SUFFIX_VERTEX_SOURCE = "vp" ; /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in binary: bvp */ public static final String SUFFIX_VERTEX_BINARY = "bvp" ; /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in source code: gp */ public static final String SUFFIX_GEOMETRY_SOURCE = "gp" ; /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in binary: bgp */ public static final String SUFFIX_GEOMETRY_BINARY = "bgp" ; /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in source code: fp */ public static final String SUFFIX_FRAGMENT_SOURCE = "fp" ; /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in binary: bfp */ public static final String SUFFIX_FRAGMENT_BINARY = "bfp" ; /** Unique relative path for binary shader resources for {@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: nvidia */ public static final String SUB_PATH_NVIDIA = "nvidia" ; /** * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param count number of shaders * @param source CharSequence array containing the shader sources, organized as source[count][strings-per-shader]. * May be either an immutable String - or mutable StringBuilder array. * * @throws IllegalArgumentException if count and source.length do not match */ public ShaderCode(int type, int count, CharSequence[][] source) { if(source.length != count) { throw new IllegalArgumentException("shader number ("+count+") and sourceFiles array ("+source.length+") of different lenght."); } switch (type) { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: case GL3.GL_GEOMETRY_SHADER: break; default: throw new GLException("Unknown shader type: "+type); } shaderSource = source; shaderBinaryFormat = -1; shaderBinary = null; shaderType = type; shader = Buffers.newDirectIntBuffer(count); id = getNextID(); if(DEBUG_CODE) { System.out.println("Created: "+toString()); // dumpShaderSource(System.out); // already done in readShaderSource } } /** * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param count number of shaders * @param binary binary buffer containing the shader binaries, */ public ShaderCode(int type, int count, int binFormat, Buffer binary) { switch (type) { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: case GL3.GL_GEOMETRY_SHADER: break; default: throw new GLException("Unknown shader type: "+type); } shaderSource = null; shaderBinaryFormat = binFormat; shaderBinary = binary; shaderType = type; shader = Buffers.newDirectIntBuffer(count); id = getNextID(); } /** * Creates a complete {@link ShaderCode} object while reading all shader source of sourceFiles, * which location is resolved using the context class, see {@link #readShaderSource(Class, String)}. * * @param gl current GL object to determine whether a shader compiler is available. If null, no validation is performed. * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param count number of shaders * @param context class used to help resolving the source location * @param sourceFiles array of source locations, organized as sourceFiles[count] * @param mutableStringBuilder if true method returns a mutable StringBuilder instance * which can be edited later on at the costs of a String conversion when passing to * {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)}. * If false method returns an immutable String instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if count and sourceFiles.length do not match * @see #readShaderSource(Class, String) */ public static ShaderCode create(GL2ES2 gl, int type, int count, Class context, String[] sourceFiles, boolean mutableStringBuilder) { if(null != gl && !ShaderUtil.isShaderCompilerAvailable(gl)) { return null; } CharSequence[][] shaderSources = null; if(null!=sourceFiles) { // sourceFiles.length and count is validated in ctor shaderSources = new CharSequence[sourceFiles.length][1]; for(int i=0; ibinaryFile, * which location is resolved using the context class, see {@link #readShaderBinary(Class, String)}. * * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param count number of shaders * @param context class used to help resolving the source location * @param binFormat a valid native binary format as they can be queried by {@link ShaderUtil#getShaderBinaryFormats(GL)}. * @param sourceFiles array of source locations, organized as sourceFiles[count] * * @see #readShaderBinary(Class, String) * @see ShaderUtil#getShaderBinaryFormats(GL) */ public static ShaderCode create(int type, int count, Class context, int binFormat, String binaryFile) { ByteBuffer shaderBinary = null; if(null!=binaryFile && 0<=binFormat) { try { shaderBinary = readShaderBinary(context, binaryFile); } catch (IOException ioe) { throw new RuntimeException("readShaderBinary("+binaryFile+") error: ", ioe); } if(null == shaderBinary) { binFormat = -1; } } if(null==shaderBinary) { return null; } return new ShaderCode(type, count, binFormat, shaderBinary); } /** * Returns a unique suffix for shader resources as follows: * * @param binary true for a binary resource, false for a source resource * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * * @throws GLException if type is not supported * * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getFileSuffix(boolean binary, int type) { switch (type) { case GL2ES2.GL_VERTEX_SHADER: return binary?SUFFIX_VERTEX_BINARY:SUFFIX_VERTEX_SOURCE; case GL2ES2.GL_FRAGMENT_SHADER: return binary?SUFFIX_FRAGMENT_BINARY:SUFFIX_FRAGMENT_SOURCE; case GL3.GL_GEOMETRY_SHADER: return binary?SUFFIX_GEOMETRY_BINARY:SUFFIX_GEOMETRY_SOURCE; default: throw new GLException("illegal shader type: "+type); } } /** * Returns a unique relative path for binary shader resources as follows: * * * @throws GLException if binFormat is not supported * * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getBinarySubPath(int binFormat) { switch (binFormat) { case GLES2.GL_NVIDIA_PLATFORM_BINARY_NV: return SUB_PATH_NVIDIA; default: throw new GLException("unsupported binary format: "+binFormat); } } /** * Convenient creation method for instantiating a complete {@link ShaderCode} object * either from source code using {@link #create(GL2ES2, int, int, Class, String[])}, * or from a binary code using {@link #create(int, int, Class, int, String)}, * whatever is available first. *

* The source and binary location names are expected w/o suffixes which are * resolved and appended using {@link #getFileSuffix(boolean, int)}. *

*

* Additionally, the binary resource is expected within a subfolder of binRoot * which reflects the vendor specific binary format, see {@link #getBinarySubPath(int)}. * All {@link ShaderUtil#getShaderBinaryFormats(GL)} are being iterated * using the binary subfolder, the first existing resource is being used. *

* * Example: *
     *   Your std JVM layout (plain or within a JAR):
     *
     *      org/test/glsl/MyShaderTest.class
     *      org/test/glsl/shader/vertex.vp
     *      org/test/glsl/shader/fragment.fp
     *      org/test/glsl/shader/bin/nvidia/vertex.bvp
     *      org/test/glsl/shader/bin/nvidia/fragment.bfp
     *
     *   Your Android APK layout:
     *
     *      classes.dex
     *      assets/org/test/glsl/shader/vertex.vp
     *      assets/org/test/glsl/shader/fragment.fp
     *      assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
     *      assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
     *      ...
     *
     *   Your invocation in org/test/glsl/MyShaderTest.java:
     *
     *      ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, 1, this.getClass(),
     *                                         "shader", new String[] { "vertex" }, "shader/bin", "vertex");
     *      ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(),
     *                                         "shader", new String[] { "vertex" }, "shader/bin", "fragment");
     *      ShaderProgram sp0 = new ShaderProgram();
     *      sp0.add(gl, vp0, System.err);
     *      sp0.add(gl, fp0, System.err);
     *      st.attachShaderProgram(gl, sp0, true);
     * 
* A simplified entry point is {@link #create(GL2ES2, int, Class, String, String, String, boolean)}. * *

* The location is finally being resolved using the context class, see {@link #readShaderBinary(Class, String)}. *

* * @param gl current GL object to determine whether a shader compiler is available (if source is used), * or to determine the shader binary format (if binary is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param count number of shaders * @param context class used to help resolving the source and binary location * @param srcRoot relative root path for srcBasenames optional * @param srcBasenames basenames w/o path or suffix relative to srcRoot for the shader's source code * @param binRoot relative root path for binBasenames * @param binBasename basename w/o path or suffix relative to binRoot for the shader's binary code * @param mutableStringBuilder if true method returns a mutable StringBuilder instance * which can be edited later on at the costs of a String conversion when passing to * {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)}. * If false method returns an immutable String instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if count and srcBasenames.length do not match * * @see #create(GL2ES2, int, int, Class, String[]) * @see #create(int, int, Class, int, String) * @see #readShaderSource(Class, String) * @see #getFileSuffix(boolean, int) * @see ShaderUtil#getShaderBinaryFormats(GL) * @see #getBinarySubPath(int) */ public static ShaderCode create(GL2ES2 gl, int type, int count, Class context, String srcRoot, String[] srcBasenames, String binRoot, String binBasename, boolean mutableStringBuilder) { ShaderCode res = null; final String srcPath[]; String srcPathString = null; String binFileName = null; if( null!=srcBasenames && ShaderUtil.isShaderCompilerAvailable(gl) ) { srcPath = new String[srcBasenames.length]; final String srcSuffix = getFileSuffix(false, type); if( null != srcRoot && srcRoot.length() > 0 ) { for(int i=0; i binFmts = ShaderUtil.getShaderBinaryFormats(gl); final String binSuffix = getFileSuffix(true, type); for(Iterator iter=binFmts.iterator(); iter.hasNext(); ) { int bFmt = iter.next().intValue(); final String bFmtPath = getBinarySubPath(bFmt); if(null==bFmtPath) continue; binFileName = binRoot + '/' + bFmtPath + '/' + binBasename + "." + binSuffix; res = create(type, count, context, bFmt, binFileName); if(null!=res) { return res; } } } throw new GLException("No shader code found (source nor binary) for src: "+srcPathString+ ", bin: "+binFileName); } /** * Simplified variation of {@link #create(GL2ES2, int, int, Class, String, String[], String, String)}. *
* * Example: *
     *   Your std JVM layout (plain or within a JAR):
     *
     *      org/test/glsl/MyShaderTest.class
     *      org/test/glsl/shader/vertex.vp
     *      org/test/glsl/shader/fragment.fp
     *      org/test/glsl/shader/bin/nvidia/vertex.bvp
     *      org/test/glsl/shader/bin/nvidia/fragment.bfp
     *
     *   Your Android APK layout:
     *
     *      classes.dex
     *      assets/org/test/glsl/shader/vertex.vp
     *      assets/org/test/glsl/shader/fragment.fp
     *      assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
     *      assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
     *      ...
     *
     *   Your invocation in org/test/glsl/MyShaderTest.java:
     *
     *      ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(),
     *                                         "shader", "shader/bin", "vertex");
     *      ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(),
     *                                         "shader", "shader/bin", "fragment");
     *      ShaderProgram sp0 = new ShaderProgram();
     *      sp0.add(gl, vp0, System.err);
     *      sp0.add(gl, fp0, System.err);
     *      st.attachShaderProgram(gl, sp0, true);
     * 
* * @param gl current GL object to determine whether a shader compiler is available (if source is used), * or to determine the shader binary format (if binary is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} * @param context class used to help resolving the source and binary location * @param srcRoot relative root path for basename optional * @param binRoot relative root path for basename * @param mutableStringBuilder TODO * @param basenames basename w/o path or suffix relative to srcRoot and binRoot * for the shader's source and binary code. * @param mutableStringBuilder if true method returns a mutable StringBuilder instance * which can be edited later on at the costs of a String conversion when passing to * {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)}. * If false method returns an immutable String instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @throws IllegalArgumentException if count is not 1 * * @see #create(GL2ES2, int, int, Class, String, String[], String, String) */ public static ShaderCode create(GL2ES2 gl, int type, Class context, String srcRoot, String binRoot, String basename, boolean mutableStringBuilder) { return create(gl, type, 1, context, srcRoot, new String[] { basename }, binRoot, basename, mutableStringBuilder ); } /** * returns the uniq shader id as an integer */ public int id() { return id; } public int shaderType() { return shaderType; } public String shaderTypeStr() { return shaderTypeStr(shaderType); } public static String shaderTypeStr(int type) { switch (type) { case GL2ES2.GL_VERTEX_SHADER: return "VERTEX_SHADER"; case GL2ES2.GL_FRAGMENT_SHADER: return "FRAGMENT_SHADER"; case GL3.GL_GEOMETRY_SHADER: return "GEOMETRY_SHADER"; } return "UNKNOWN_SHADER"; } public int shaderBinaryFormat() { return shaderBinaryFormat; } public Buffer shaderBinary() { return shaderBinary; } public CharSequence[][] shaderSource() { return shaderSource; } public boolean isValid() { return valid; } public IntBuffer shader() { return shader; } public boolean compile(GL2ES2 gl) { return compile(gl, null); } public boolean compile(GL2ES2 gl, PrintStream verboseOut) { if(isValid()) return true; // Create & Compile the vertex/fragment shader objects if(null!=shaderSource) { if(DEBUG_CODE) { System.err.println("ShaderCode.compile:"); dumpShaderSource(System.err); } valid=ShaderUtil.createAndCompileShader(gl, shader, shaderType, shaderSource, verboseOut); } else if(null!=shaderBinary) { valid=ShaderUtil.createAndLoadShader(gl, shader, shaderType, shaderBinaryFormat, shaderBinary, verboseOut); } else { throw new GLException("no code (source or binary)"); } return valid; } public void destroy(GL2ES2 gl) { if(isValid()) { if(null!=gl) { ShaderUtil.deleteShader(gl, shader()); } valid=false; } if(null!=shaderBinary) { shaderBinary.clear(); shaderBinary=null; } shaderSource=null; shaderBinaryFormat=-1; shaderType=-1; id=-1; } @Override public boolean equals(Object obj) { if(this==obj) { return true; } if(obj instanceof ShaderCode) { return id()==((ShaderCode)obj).id(); } return false; } @Override public int hashCode() { return id; } @Override public String toString() { StringBuilder buf = new StringBuilder("ShaderCode[id="+id+", type="+shaderTypeStr()+", valid="+valid+", shader: "); for(int i=0; i"); return; } final int sourceCount = (null!=shaderSource)?shaderSource.length:0; final int shaderCount = (null!=shader)?shader.capacity():0; for(int i=0; i=sourceCount) { out.println(""); } else { CharSequence[] src = shaderSource[i]; int lineno=0; for(int j=0; jdata after the line containing tag. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* * @param shaderIdx the shader index to be used. * @param tag search string * @param fromIndex start search tag begininig with this index * @param data the text to be inserted. Shall end with an EOL '\n' character. * @return index after the inserted data * * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type StringBuilder */ public int insertShaderSource(int shaderIdx, String tag, int fromIndex, CharSequence data) { if(null==shaderSource) { throw new IllegalStateException("no shader source"); } final int shaderCount = (null!=shader)?shader.capacity():0; if(0>shaderIdx || shaderIdx>=shaderCount) { throw new IndexOutOfBoundsException("shaderIdx not within shader bounds [0.."+(shaderCount-1)+"]: "+shaderIdx); } final int sourceCount = (null!=shaderSource)?shaderSource.length:0; if(shaderIdx>=sourceCount) { throw new IndexOutOfBoundsException("shaderIdx not within source bounds [0.."+(sourceCount-1)+"]: "+shaderIdx); } final CharSequence[] src = shaderSource[shaderIdx]; int curEndIndex = 0; for(int j=0; jeol) { eol = sb.indexOf("\r", insertIdx); // eol: covers \r 'only' } if(0oldName
with newName in all shader sources. *

* In case oldName and newName are equal, no action is performed. *

*

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* * @param oldName the to be replace string * @param newName the replacement string * @return the number of replacements * * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type StringBuilder */ public int replaceInShaderSource(String oldName, String newName) { if(null==shaderSource) { throw new IllegalStateException("no shader source"); } if(oldName == newName || oldName.equals(newName)) { return 0; } final int oldNameLen = oldName.length(); final int newNameLen = newName.length(); int num = 0; final int sourceCount = (null!=shaderSource)?shaderSource.length:0; for(int shaderIdx = 0; shaderIdxdata
at position in shader source for shader shaderIdx. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* * @param shaderIdx the shader index to be used. * @param position in shader source segments of shader shaderIdx, -1 will append data * @param data the text to be inserted. Shall end with an EOL '\n' character * @return index after the inserted data * * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type StringBuilder */ public int insertShaderSource(int shaderIdx, int position, CharSequence data) { if(null==shaderSource) { throw new IllegalStateException("no shader source"); } final int shaderCount = (null!=shader)?shader.capacity():0; if(0>shaderIdx || shaderIdx>=shaderCount) { throw new IndexOutOfBoundsException("shaderIdx not within shader bounds [0.."+(shaderCount-1)+"]: "+shaderIdx); } final int sourceCount = (null!=shaderSource)?shaderSource.length:0; if(shaderIdx>=sourceCount) { throw new IndexOutOfBoundsException("shaderIdx not within source bounds [0.."+(sourceCount-1)+"]: "+shaderIdx); } final CharSequence[] src = shaderSource[shaderIdx]; int curEndIndex = 0; for(int j=0; j position && j == src.length - 1 ) { position = curEndIndex; } if(0 <= position && position <= curEndIndex ) { sb.insert(position, data); return position+data.length(); } } return -1; } /** * Adds shader source located in path, * either relative to the context class or absolute as-is * at position in shader source for shader shaderIdx. *

* Final location lookup is performed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. *

*

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* * @param shaderIdx the shader index to be used. * @param position in shader source segments of shader shaderIdx, -1 will append data * @param context class used to help resolve the source location * @param path location of shader source * @return index after the inserted code. * @throws IOException * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type StringBuilder * @see IOUtil#getResource(Class, String) */ public int insertShaderSource(int shaderIdx, int position, Class context, String path) throws IOException { final CharSequence data = readShaderSource(context, path, true); if( null != data ) { return insertShaderSource(shaderIdx, position, data); } else { return position; } } @SuppressWarnings("resource") private static int readShaderSource(Class context, URLConnection conn, StringBuilder result, int lineno) throws IOException { if(DEBUG_CODE) { if(0 == lineno) { result.append("// "+conn.getURL().toExternalForm()+"\n"); } else { result.append("// included @ line "+lineno+": "+conn.getURL().toExternalForm()+"\n"); } } final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); try { String line = null; while ((line = reader.readLine()) != null) { lineno++; if (line.startsWith("#include ")) { String includeFile = line.substring(9).trim(); URLConnection nextConn = null; // Try relative of current shader location nextConn = IOUtil.openURL(IOUtil.getRelativeOf(conn.getURL(), includeFile), "ShaderCode.relativeOf "); if (nextConn == null) { // Try relative of class and absolute nextConn = IOUtil.getResource(context, includeFile); } if (nextConn == null) { // Fail throw new FileNotFoundException("Can't find include file " + includeFile); } lineno = readShaderSource(context, nextConn, result, lineno); } else { result.append(line + "\n"); } } } finally { IOUtil.close(reader, false); } return lineno; } /** * * @param context * @param conn * @param result * @throws IOException */ public static void readShaderSource(Class context, URLConnection conn, StringBuilder result) throws IOException { readShaderSource(context, conn, result, 0); } /** * Reads shader source located in path, * either relative to the context class or absolute as-is. *

* Final location lookup is performed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. *

* * @param context class used to help resolve the source location * @param path location of shader source * @param mutableStringBuilder if true method returns a mutable StringBuilder instance * which can be edited later on at the costs of a String conversion when passing to * {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)}. * If false method returns an immutable String instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @throws IOException * * @see IOUtil#getResource(Class, String) */ public static CharSequence readShaderSource(Class context, String path, boolean mutableStringBuilder) throws IOException { URLConnection conn = IOUtil.getResource(context, path); if (conn == null) { return null; } StringBuilder result = new StringBuilder(); readShaderSource(context, conn, result); return mutableStringBuilder ? result : result.toString(); } /** * Reads shader binary located in path, * either relative to the context class or absolute as-is. *

* Final location lookup is perfomed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. *

* * @param context class used to help resolve the source location * @param path location of shader binary * @throws IOException * * @see IOUtil#getResource(Class, String) */ public static ByteBuffer readShaderBinary(Class context, String path) throws IOException { final URLConnection conn = IOUtil.getResource(context, path); if (conn == null) { return null; } final BufferedInputStream bis = new BufferedInputStream( conn.getInputStream() ); try { return IOUtil.copyStream2ByteBuffer( bis ); } finally { IOUtil.close(bis, false); } } // Shall we use: #ifdef GL_FRAGMENT_PRECISION_HIGH .. #endif for using highp in fragment shader if avail ? /** Default precision of {@link GL#isGLES2() ES2} for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}: {@value #es2_default_precision_vp} */ public static final String es2_default_precision_vp = "\nprecision highp float;\nprecision highp int;\n/*precision lowp sampler2D;*/\n/*precision lowp samplerCube;*/\n"; /** Default precision of {@link GL#isGLES2() ES2} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es2_default_precision_fp} */ public static final String es2_default_precision_fp = "\nprecision mediump float;\nprecision mediump int;\n/*precision lowp sampler2D;*/\n/*precision lowp samplerCube;*/\n"; /** Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}: {@value #es3_default_precision_vp} */ public static final String es3_default_precision_vp = es2_default_precision_vp; /** Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es3_default_precision_fp} */ public static final String es3_default_precision_fp = es2_default_precision_fp; /** Default precision of GLSL ≥ 1.30 as required until < 1.50 for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader} or {@link GL3#GL_GEOMETRY_SHADER geometry-shader}: {@value #gl3_default_precision_vp_gp}. See GLSL Spec 1.30-1.50 Section 4.5.3. */ public static final String gl3_default_precision_vp_gp = "\nprecision highp float;\nprecision highp int;\n"; /** Default precision of GLSL ≥ 1.30 as required until < 1.50 for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #gl3_default_precision_fp}. See GLSL Spec 1.30-1.50 Section 4.5.3. */ public static final String gl3_default_precision_fp = "\nprecision highp float;\nprecision mediump int;\n/*precision mediump sampler2D;*/\n"; /** Behavior for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String REQUIRE = "require"; /** Behavior for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String ENABLE = "enable"; /** Behavior for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String DISABLE = "disable"; /** Behavior for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String WARN = "warn"; /** * Creates a GLSL extension directive. *

* Prefer {@link #ENABLE} over {@link #REQUIRE}, since the latter will force a failure if not supported. *

* * @param extensionName * @param behavior shall be either {@link #REQUIRE}, {@link #ENABLE}, {@link #DISABLE} or {@link #WARN} * @return the complete extension directive */ public static String createExtensionDirective(String extensionName, String behavior) { return "#extension " + extensionName + " : " + behavior + "\n"; } /** * Add GLSL version at the head of this shader source code. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* @param gl a GL context, which must have been made current once * @return the index after the inserted data, maybe 0 if nothing has be inserted. */ public final int addGLSLVersion(GL2ES2 gl) { return insertShaderSource(0, 0, gl.getContext().getGLSLVersionString()); } /** * Adds default precision to source code at given position if required, i.e. * {@link #es2_default_precision_vp}, {@link #es2_default_precision_fp}, * {@link #gl3_default_precision_vp_gp}, {@link #gl3_default_precision_fp} or none, * depending on the {@link GLContext#getGLSLVersionNumber() GLSL version} being used. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* @param gl a GL context, which must have been made current once * @param pos position within this mutable shader source. * @return the index after the inserted data, maybe 0 if nothing has be inserted. */ public final int addDefaultShaderPrecision(GL2ES2 gl, int pos) { final String defaultPrecision; if( gl.isGLES2() ) { switch ( shaderType ) { case GL2ES2.GL_VERTEX_SHADER: defaultPrecision = es2_default_precision_vp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = es2_default_precision_fp; break; default: defaultPrecision = null; break; } } else if( gl.isGLES3() ) { switch ( shaderType ) { case GL2ES2.GL_VERTEX_SHADER: defaultPrecision = es3_default_precision_vp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = es3_default_precision_fp; break; default: defaultPrecision = null; break; } } else if( requiresGL3DefaultPrecision(gl) ) { // GLSL [ 1.30 .. 1.50 [ needs at least fragement float default precision! switch ( shaderType ) { case GL2ES2.GL_VERTEX_SHADER: case GL3.GL_GEOMETRY_SHADER: defaultPrecision = gl3_default_precision_vp_gp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = gl3_default_precision_fp; break; default: defaultPrecision = null; break; } } else { defaultPrecision = null; } if( null != defaultPrecision ) { pos = insertShaderSource(0, pos, defaultPrecision); } return pos; } /** Returns true, if GLSL version requires default precision, i.e. ES2 or GLSL [1.30 .. 1.50[. */ public static final boolean requiresDefaultPrecision(GL2ES2 gl) { if( gl.isGLES() ) { return true; } return requiresGL3DefaultPrecision(gl); } /** Returns true, if GL3 GLSL version requires default precision, i.e. GLSL [1.30 .. 1.50[. */ public static final boolean requiresGL3DefaultPrecision(GL2ES2 gl) { if( gl.isGL3() ) { final VersionNumber glslVersion = gl.getContext().getGLSLVersionNumber(); return glslVersion.compareTo(GLContext.Version130) >= 0 && glslVersion.compareTo(GLContext.Version150) < 0 ; } else { return false; } } /** * Default customization of this shader source code. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* @param gl a GL context, which must have been made current once * @param preludeVersion if true {@link GLContext#getGLSLVersionString()} is preluded, otherwise not. * @param addDefaultPrecision if true default precision source code line(s) are added, i.e. * {@link #es2_default_precision_vp}, {@link #es2_default_precision_fp}, * {@link #gl3_default_precision_vp_gp}, {@link #gl3_default_precision_fp} or none, * depending on the {@link GLContext#getGLSLVersionNumber() GLSL version} being used. * @return the index after the inserted data, maybe 0 if nothing has be inserted. * @see #addGLSLVersion(GL2ES2) * @see #addDefaultShaderPrecision(GL2ES2, int) */ public final int defaultShaderCustomization(GL2ES2 gl, boolean preludeVersion, boolean addDefaultPrecision) { int pos; if( preludeVersion ) { pos = addGLSLVersion(gl); } else { pos = 0; } if( addDefaultPrecision ) { pos = addDefaultShaderPrecision(gl, pos); } return pos; } /** * Default customization of this shader source code. *

* Note: The shader source to be edit must be created using a mutable StringBuilder. *

* @param gl a GL context, which must have been made current once * @param preludeVersion if true {@link GLContext#getGLSLVersionString()} is preluded, otherwise not. * @param esDefaultPrecision optional default precision source code line(s) preluded if not null and if {@link GL#isGLES()}. * You may use {@link #es2_default_precision_fp} for fragment shader and {@link #es2_default_precision_vp} for vertex shader. * @return the index after the inserted data, maybe 0 if nothing has be inserted. * @see #addGLSLVersion(GL2ES2) * @see #addDefaultShaderPrecision(GL2ES2, int) */ public final int defaultShaderCustomization(GL2ES2 gl, boolean preludeVersion, String esDefaultPrecision) { int pos; if( preludeVersion ) { pos = addGLSLVersion(gl); } else { pos = 0; } if( gl.isGLES() && null != esDefaultPrecision ) { pos = insertShaderSource(0, pos, esDefaultPrecision); } else { pos = addDefaultShaderPrecision(gl, pos); } return pos; } //---------------------------------------------------------------------- // Internals only below this point // protected CharSequence[][] shaderSource = null; protected Buffer shaderBinary = null; protected int shaderBinaryFormat = -1; protected IntBuffer shader = null; protected int shaderType = -1; protected int id = -1; protected boolean valid=false; private static synchronized int getNextID() { return nextID++; } protected static int nextID = 1; }