/** * 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.URISyntaxException; 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 com.jogamp.opengl.GL; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GL3; import com.jogamp.opengl.GL3ES3; import com.jogamp.opengl.GL4; import com.jogamp.opengl.GLES2; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLException; import jogamp.opengl.Debug; import com.jogamp.common.net.Uri; 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. * <p> * 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}. * </p> * <p> * Support for {@link GL4#GL_TESS_CONTROL_SHADER} and {@link GL4#GL_TESS_EVALUATION_SHADER} * was added since 2.2.1. * </p> */ 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: <code>{@value}</code> */ public static final String SUFFIX_VERTEX_SOURCE = "vp" ; /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_VERTEX_BINARY = "bvp" ; /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in source code: <code>{@value}</code> */ public static final String SUFFIX_GEOMETRY_SOURCE = "gp" ; /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_GEOMETRY_BINARY = "bgp" ; /** * Unique resource suffix for {@link GL3ES3#GL_COMPUTE_SHADER} in source code: <code>{@value}</code> * @since 2.3.2 */ public static final String SUFFIX_COMPUTE_SOURCE = "cp" ; /** * Unique resource suffix for {@link GL3ES3#GL_COMPUTE_SHADER} in binary: <code>{@value}</code> * @since 2.3.2 */ public static final String SUFFIX_COMPUTE_BINARY = "bcp" ; /** * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in source code: <code>{@value}</code> * @since 2.2.1 */ public static final String SUFFIX_TESS_CONTROL_SOURCE = "tcp" ; /** * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in binary: <code>{@value}</code> * @since 2.2.1 */ public static final String SUFFIX_TESS_CONTROL_BINARY = "btcp" ; /** * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in source code: <code>{@value}</code> * @since 2.2.1 */ public static final String SUFFIX_TESS_EVALUATION_SOURCE = "tep" ; /** * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in binary: <code>{@value}</code> * @since 2.2.1 */ public static final String SUFFIX_TESS_EVALUATION_BINARY = "btep" ; /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in source code: <code>{@value}</code> */ public static final String SUFFIX_FRAGMENT_SOURCE = "fp" ; /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_FRAGMENT_BINARY = "bfp" ; /** Unique relative path for binary shader resources for {@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: <code>{@value}</code> */ public static final String SUB_PATH_NVIDIA = "nvidia" ; /** * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param source CharSequence array containing the shader sources, organized as <code>source[count][strings-per-shader]</code>. * May be either an immutable <code>String</code> - or mutable <code>StringBuilder</code> array. * * @throws IllegalArgumentException if <code>count</code> and <code>source.length</code> do not match */ public ShaderCode(final int type, final int count, final 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: case GL3.GL_TESS_CONTROL_SHADER: case GL3.GL_TESS_EVALUATION_SHADER: case GL3ES3.GL_COMPUTE_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}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param binary binary buffer containing the shader binaries, */ public ShaderCode(final int type, final int count, final int binFormat, final Buffer binary) { switch (type) { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: case GL3.GL_GEOMETRY_SHADER: case GL3.GL_TESS_CONTROL_SHADER: case GL3.GL_TESS_EVALUATION_SHADER: case GL3ES3.GL_COMPUTE_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 <code>sourceFiles</code>, * which location is resolved using the <code>context</code> 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}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param context class used to help resolving the source location * @param sourceFiles array of source locations, organized as <code>sourceFiles[count]</code> -> <code>shaderSources[count][1]</code> * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if <code>count</code> and <code>sourceFiles.length</code> do not match * @see #readShaderSource(Class, String) */ public static ShaderCode create(final GL2ES2 gl, final int type, final int count, final Class<?> context, final String[] sourceFiles, final 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; i<sourceFiles.length; i++) { try { shaderSources[i][0] = readShaderSource(context, sourceFiles[i], mutableStringBuilder); } catch (final IOException ioe) { throw new RuntimeException("readShaderSource("+sourceFiles[i]+") error: ", ioe); } if(null == shaderSources[i][0]) { shaderSources = null; } } } if(null==shaderSources) { return null; } return new ShaderCode(type, count, shaderSources); } /** * Creates a complete {@link ShaderCode} object while reading all shader sources from {@link Uri} <code>sourceLocations</code> * via {@link #readShaderSource(Uri, boolean)}. * * @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}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param sourceLocations array of {@link Uri} source locations, organized as <code>sourceFiles[count]</code> -> <code>shaderSources[count][1]</code> * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if <code>count</code> and <code>sourceFiles.length</code> do not match * @see #readShaderSource(Uri, boolean) * @since 2.3.2 */ public static ShaderCode create(final GL2ES2 gl, final int type, final int count, final Uri[] sourceLocations, final boolean mutableStringBuilder) { if(null != gl && !ShaderUtil.isShaderCompilerAvailable(gl)) { return null; } CharSequence[][] shaderSources = null; if(null!=sourceLocations) { // sourceFiles.length and count is validated in ctor shaderSources = new CharSequence[sourceLocations.length][1]; for(int i=0; i<sourceLocations.length; i++) { try { shaderSources[i][0] = readShaderSource(sourceLocations[i], mutableStringBuilder); } catch (final IOException ioe) { throw new RuntimeException("readShaderSource("+sourceLocations[i]+") error: ", ioe); } if(null == shaderSources[i][0]) { shaderSources = null; } } } if(null==shaderSources) { return null; } return new ShaderCode(type, count, shaderSources); } /** * Creates a complete {@link ShaderCode} object while reading the shader binary of <code>binaryFile</code>, * which location is resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}. * * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_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 <code>sourceFiles[count]</code> * * @see #readShaderBinary(Class, String) * @see ShaderUtil#getShaderBinaryFormats(GL) */ public static ShaderCode create(final int type, final int count, final Class<?> context, int binFormat, final String binaryFile) { ByteBuffer shaderBinary = null; if(null!=binaryFile && 0<=binFormat) { try { shaderBinary = readShaderBinary(context, binaryFile); } catch (final 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); } /** * Creates a complete {@link ShaderCode} object while reading the shader binary from {@link Uri} <code>binLocations</code> * via {@link #readShaderBinary(Uri)}. * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param binFormat a valid native binary format as they can be queried by {@link ShaderUtil#getShaderBinaryFormats(GL)}. * @param binLocations {@link Uri} binary location * * @see #readShaderBinary(Uri) * @see ShaderUtil#getShaderBinaryFormats(GL) * @since 2.3.2 */ public static ShaderCode create(final int type, final int count, int binFormat, final Uri binLocation) { ByteBuffer shaderBinary = null; if(null!=binLocation && 0<=binFormat) { try { shaderBinary = readShaderBinary(binLocation); } catch (final IOException ioe) { throw new RuntimeException("readShaderBinary("+binLocation+") 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: * <ul> * <li>Source<ul> * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_SOURCE}</li> * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_SOURCE}</li> * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_SOURCE}</li> * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_SOURCE}</li> * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_SOURCE}</li> * <li>{@link GL3ES3#GL_COMPUTE_SHADER}: {@link #SUFFIX_COMPUTE_SOURCE}</li> * </ul></li> * <li>Binary<ul> * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_BINARY}</li> * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_BINARY}</li> * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_BINARY}</li> * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_BINARY}</li> * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_BINARY}</li> * <li>{@link GL3ES3#GL_COMPUTE_SHADER}: {@link #SUFFIX_COMPUTE_BINARY}</li> * </ul></li> * </ul> * @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}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * * @throws GLException if <code>type</code> is not supported * * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getFileSuffix(final boolean binary, final 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; case GL3.GL_TESS_CONTROL_SHADER: return binary?SUFFIX_TESS_CONTROL_BINARY:SUFFIX_TESS_CONTROL_SOURCE; case GL3.GL_TESS_EVALUATION_SHADER: return binary?SUFFIX_TESS_EVALUATION_BINARY:SUFFIX_TESS_EVALUATION_SOURCE; case GL3ES3.GL_COMPUTE_SHADER: return binary?SUFFIX_COMPUTE_BINARY:SUFFIX_COMPUTE_SOURCE; default: throw new GLException("illegal shader type: "+type); } } /** * Returns a unique relative path for binary shader resources as follows: * <ul> * <li>{@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: {@link #SUB_PATH_NVIDIA}</li> * </ul> * * @throws GLException if <code>binFormat</code> is not supported * * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getBinarySubPath(final 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. * <p> * The source and binary location names are expected w/o suffixes which are * resolved and appended using the given {@code srcSuffixOpt} and {@code binSuffixOpt} * if not {@code null}, otherwise {@link #getFileSuffix(boolean, int)} determines the suffixes. * </p> * <p> * Additionally, the binary resource is expected within a subfolder of <code>binRoot</code> * 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. * </p> * * Example: * <pre> * 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" }, null, * "shader/bin", "vertex", null, true); * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(), * "shader", new String[] { "vertex" }, null, * "shader/bin", "fragment", null, true); * ShaderProgram sp0 = new ShaderProgram(); * sp0.add(gl, vp0, System.err); * sp0.add(gl, fp0, System.err); * st.attachShaderProgram(gl, sp0, true); * </pre> * A simplified entry point is {@link #create(GL2ES2, int, Class, String, String, String, boolean)}. * * <p> * The location is finally being resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}. * </p> * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>srcBasenames</code> optional * @param srcBasenames basenames w/o path or suffix relative to <code>srcRoot</code> for the shader's source code * @param srcSuffixOpt optional custom suffix for shader's source file, * if {@code null} {@link #getFileSuffix(boolean, int)} is being used. * @param binRoot relative <i>root</i> path for <code>binBasenames</code> * @param binBasename basename w/o path or suffix relative to <code>binRoot</code> for the shader's binary code * @param binSuffixOpt optional custom suffix for shader's binary file, * if {@code null} {@link #getFileSuffix(boolean, int)} is being used. * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if <code>count</code> and <code>srcBasenames.length</code> 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) * * @since 2.3.2 */ public static ShaderCode create(final GL2ES2 gl, final int type, final int count, final Class<?> context, final String srcRoot, final String[] srcBasenames, final String srcSuffixOpt, final String binRoot, final String binBasename, final String binSuffixOpt, final 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 = null != srcSuffixOpt ? srcSuffixOpt : getFileSuffix(false, type); if( null != srcRoot && srcRoot.length() > 0 ) { for(int i=0; i<srcPath.length; i++) { srcPath[i] = srcRoot + '/' + srcBasenames[i] + "." + srcSuffix; } } else { for(int i=0; i<srcPath.length; i++) { srcPath[i] = srcBasenames[i] + "." + srcSuffix; } } res = create(gl, type, count, context, srcPath, mutableStringBuilder); if(null!=res) { return res; } srcPathString = Arrays.toString(srcPath); } else { srcPath = null; } if( null!=binBasename ) { final Set<Integer> binFmts = ShaderUtil.getShaderBinaryFormats(gl); final String binSuffix = null != binSuffixOpt ? binSuffixOpt : getFileSuffix(true, type); for(final Iterator<Integer> iter=binFmts.iterator(); iter.hasNext(); ) { final 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, String, String, boolean)}. * <p> * 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. * </p> * <p> * The source and binary location names are expected w/o suffixes which are * resolved and appended using {@link #getFileSuffix(boolean, int)}. * </p> * <p> * Additionally, the binary resource is expected within a subfolder of <code>binRoot</code> * 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. * </p> * * Example: * <pre> * 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", true); * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(), * "shader", new String[] { "vertex" }, "shader/bin", "fragment", true); * ShaderProgram sp0 = new ShaderProgram(); * sp0.add(gl, vp0, System.err); * sp0.add(gl, fp0, System.err); * st.attachShaderProgram(gl, sp0, true); * </pre> * A simplified entry point is {@link #create(GL2ES2, int, Class, String, String, String, boolean)}. * * <p> * The location is finally being resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}. * </p> * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param count number of shaders * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>srcBasenames</code> optional * @param srcBasenames basenames w/o path or suffix relative to <code>srcRoot</code> for the shader's source code * @param binRoot relative <i>root</i> path for <code>binBasenames</code> * @param binBasename basename w/o path or suffix relative to <code>binRoot</code> for the shader's binary code * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * * @throws IllegalArgumentException if <code>count</code> and <code>srcBasenames.length</code> do not match * * @see #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean) * @see #readShaderSource(Class, String) * @see #getFileSuffix(boolean, int) * @see ShaderUtil#getShaderBinaryFormats(GL) * @see #getBinarySubPath(int) */ public static ShaderCode create(final GL2ES2 gl, final int type, final int count, final Class<?> context, final String srcRoot, final String[] srcBasenames, final String binRoot, final String binBasename, final boolean mutableStringBuilder) { return create(gl, type, count, context, srcRoot, srcBasenames, null, binRoot, binBasename, null, mutableStringBuilder); } /** * Simplified variation of {@link #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean)}. * <p> * Example: * <pre> * 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", null, null, true); * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(), * "shader", "shader/bin", "fragment", null, null, true); * ShaderProgram sp0 = new ShaderProgram(); * sp0.add(gl, vp0, System.err); * sp0.add(gl, fp0, System.err); * st.attachShaderProgram(gl, sp0, true); * </pre> * </p> * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>basename</code> optional * @param binRoot relative <i>root</i> path for <code>basename</code> * @param basename basename w/o path or suffix relative to <code>srcRoot</code> and <code>binRoot</code> * for the shader's source and binary code. * @param srcSuffixOpt optional custom suffix for shader's source file, * if {@code null} {@link #getFileSuffix(boolean, int)} is being used. * @param binSuffixOpt optional custom suffix for shader's binary file, * if {@code null} {@link #getFileSuffix(boolean, int)} is being used. * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @throws IllegalArgumentException if <code>count</code> is not 1 * * @see #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean) * @since 2.3.2 */ public static ShaderCode create(final GL2ES2 gl, final int type, final Class<?> context, final String srcRoot, final String binRoot, final String basename, final String srcSuffixOpt, final String binSuffixOpt, final boolean mutableStringBuilder) { return create(gl, type, 1, context, srcRoot, new String[] { basename }, srcSuffixOpt, binRoot, basename, binSuffixOpt, mutableStringBuilder ); } /** * Simplified variation of {@link #create(GL2ES2, int, Class, String, String, String, String, String, boolean)}. * <p> * Example: * <pre> * 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", true); * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(), * "shader", "shader/bin", "fragment", true); * ShaderProgram sp0 = new ShaderProgram(); * sp0.add(gl, vp0, System.err); * sp0.add(gl, fp0, System.err); * st.attachShaderProgram(gl, sp0, true); * </pre> * </p> * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}. * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>basename</code> optional * @param binRoot relative <i>root</i> path for <code>basename</code> * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @param basenames basename w/o path or suffix relative to <code>srcRoot</code> and <code>binRoot</code> * for the shader's source and binary code. * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @throws IllegalArgumentException if <code>count</code> is not 1 */ public static ShaderCode create(final GL2ES2 gl, final int type, final Class<?> context, final String srcRoot, final String binRoot, final String basename, final boolean mutableStringBuilder) { return ShaderCode.create(gl, type, context, srcRoot, binRoot, basename, null, null, 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(final 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"; case GL3.GL_TESS_CONTROL_SHADER: return "TESS_CONTROL_SHADER"; case GL3.GL_TESS_EVALUATION_SHADER: return "TESS_EVALUATION_SHADER"; case GL3ES3.GL_COMPUTE_SHADER: return "COMPUTE_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(final GL2ES2 gl) { return compile(gl, null); } public boolean compile(final GL2ES2 gl, final 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(final 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(final 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() { final StringBuilder buf = new StringBuilder("ShaderCode[id="+id+", type="+shaderTypeStr()+", valid="+valid+", shader: "); for(int i=0; i<shader.remaining(); i++) { buf.append(" "+shader.get(i)); } if(null!=shaderSource) { buf.append(", source]"); } else if(null!=shaderBinary) { buf.append(", binary "+shaderBinary+"]"); } return buf.toString(); } public void dumpShaderSource(final PrintStream out) { if(null==shaderSource) { out.println("<no shader source>"); return; } final int sourceCount = shaderSource.length; final int shaderCount = (null!=shader)?shader.capacity():0; for(int i=0; i<shaderCount; i++) { out.println(""); out.println("Shader #"+i+"/"+shaderCount+" name "+shader.get(i)); out.println("--------------------------------------------------------------"); if(i>=sourceCount) { out.println("<no shader source>"); } else { final CharSequence[] src = shaderSource[i]; int lineno=0; for(int j=0; j<src.length; j++) { out.printf("%4d: // Segment %d/%d: \n", lineno, j, src.length); final BufferedReader reader = new BufferedReader(new StringReader(src[j].toString())); String line = null; try { while ((line = reader.readLine()) != null) { lineno++; out.printf("%4d: %s\n", lineno, line); } } catch (final IOException e) { /* impossible .. StringReader */ } } } out.println("--------------------------------------------------------------"); } } /** * Adds <code>data</code> after the line containing <code>tag</code>. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * * @param shaderIdx the shader index to be used. * @param tag search string * @param fromIndex start search <code>tag</code> begininig with this index * @param data the text to be inserted. Shall end with an EOL '\n' character. * @return index after the inserted <code>data</code> * * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type <code>StringBuilder</code> */ public int insertShaderSource(final int shaderIdx, final String tag, final int fromIndex, final 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 = shaderSource.length; 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<src.length; j++) { if( !(src[j] instanceof StringBuilder) ) { throw new IllegalStateException("shader source not a mutable StringBuilder, but CharSequence of type: "+src[j].getClass().getName()); } final StringBuilder sb = (StringBuilder)src[j]; curEndIndex += sb.length(); if(fromIndex < curEndIndex) { int insertIdx = sb.indexOf(tag, fromIndex); if(0<=insertIdx) { insertIdx += tag.length(); int eol = sb.indexOf("\n", insertIdx); // eol: covers \n and \r\n if(0>eol) { eol = sb.indexOf("\r", insertIdx); // eol: covers \r 'only' } if(0<eol) { insertIdx = eol+1; // eol found } else { sb.insert(insertIdx, "\n"); // add eol insertIdx++; } sb.insert(insertIdx, data); return insertIdx+data.length(); } } } return -1; } /** * Replaces <code>oldName</code> with <code>newName</code> in all shader sources. * <p> * In case <code>oldName</code> and <code>newName</code> are equal, no action is performed. * </p> * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * * @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 <code>StringBuilder</code> */ public int replaceInShaderSource(final String oldName, final 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 = shaderSource.length; for(int shaderIdx = 0; shaderIdx<sourceCount; shaderIdx++) { final CharSequence[] src = shaderSource[shaderIdx]; for(int j=0; j<src.length; j++) { if( !(src[j] instanceof StringBuilder) ) { throw new IllegalStateException("shader source not a mutable StringBuilder, but CharSequence of type: "+src[j].getClass().getName()); } final StringBuilder sb = (StringBuilder)src[j]; int curPos = 0; while(curPos<sb.length()-oldNameLen+1) { final int startIdx = sb.indexOf(oldName, curPos); if(0<=startIdx) { final int endIdx = startIdx + oldNameLen; sb.replace(startIdx, endIdx, newName); curPos = startIdx + newNameLen; num++; } else { curPos = sb.length(); } } } } return num; } /** * Adds <code>data</code> at <code>position</code> in shader source for shader <code>shaderIdx</code>. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * * @param shaderIdx the shader index to be used. * @param position in shader source segments of shader <code>shaderIdx</code>, -1 will append data * @param data the text to be inserted. Shall end with an EOL '\n' character * @return index after the inserted <code>data</code> * * @throws IllegalStateException if the shader source's CharSequence is immutable, i.e. not of type <code>StringBuilder</code> */ public int insertShaderSource(final int shaderIdx, int position, final 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 = shaderSource.length; 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<src.length; j++) { if( !(src[j] instanceof StringBuilder) ) { throw new IllegalStateException("shader source not a mutable StringBuilder, but CharSequence of type: "+src[j].getClass().getName()); } final StringBuilder sb = (StringBuilder)src[j]; curEndIndex += sb.length(); if( 0 > position && j == src.length - 1 ) { position = curEndIndex; } if(0 <= position && position <= curEndIndex ) { sb.insert(position, data); return position+data.length(); } } return position; } /** * Adds shader source located in <code>path</code>, * either relative to the <code>context</code> class or absolute <i>as-is</i> * at <code>position</code> in shader source for shader <code>shaderIdx</code>. * <p> * Final location lookup is performed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. * </p> * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * * @param shaderIdx the shader index to be used. * @param position in shader source segments of shader <code>shaderIdx</code>, -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 <code>StringBuilder</code> * @see IOUtil#getResource(Class, String) */ public int insertShaderSource(final int shaderIdx, final int position, final Class<?> context, final String path) throws IOException { final CharSequence data = readShaderSource(context, path, true); if( null != data ) { return insertShaderSource(shaderIdx, position, data); } else { return position; } } private static int readShaderSource(final Class<?> context, final URLConnection conn, final 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 ")) { final String includeFile = line.substring(9).trim(); URLConnection nextConn = null; // Try relative of current shader location final Uri relUri = Uri.valueOf( conn.getURL() ).getRelativeOf(new Uri.Encoded( includeFile, Uri.PATH_LEGAL )); nextConn = IOUtil.openURL(relUri.toURL(), "ShaderCode.relativeOf "); if (nextConn == null) { // Try relative of class and absolute nextConn = IOUtil.getResource(includeFile, context.getClassLoader(), context); } if (nextConn == null) { // Fail throw new FileNotFoundException("Can't find include file " + includeFile); } lineno = readShaderSource(context, nextConn, result, lineno); } else { result.append(line + "\n"); } } } catch (final URISyntaxException e) { throw new IOException(e); } finally { IOUtil.close(reader, false); } return lineno; } /** * Reads shader source located in <code>conn</code>. * * @param context class used to help resolve the source location, may be {@code null} * @param conn the {@link URLConnection} of the shader source * @param result {@link StringBuilder} sink for the resulting shader source code * @throws IOException */ public static void readShaderSource(final Class<?> context, final URLConnection conn, final StringBuilder result) throws IOException { readShaderSource(context, conn, result, 0); } /** * Reads shader source located in <code>path</code>, * either relative to the <code>context</code> class or absolute <i>as-is</i>. * <p> * Final location lookup is performed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. * </p> * * @param context class used to help resolve the source location * @param path location of shader source * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> 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(final Class<?> context, final String path, final boolean mutableStringBuilder) throws IOException { final URLConnection conn = IOUtil.getResource(path, context.getClassLoader(), context); if (conn == null) { return null; } final StringBuilder result = new StringBuilder(); readShaderSource(context, conn, result); return mutableStringBuilder ? result : result.toString(); } /** * Reads shader source located from {@link Uri#absolute} {@link Uri} <code>sourceLocation</code>. * @param sourceLocation {@link Uri} location of shader source * @param mutableStringBuilder if <code>true</code> method returns a mutable <code>StringBuilder</code> 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 <code>false</code> method returns an immutable <code>String</code> instance, * which can be passed to {@link GL2ES2#glShaderSource(int, int, String[], IntBuffer)} * at no additional costs. * @throws IOException * @since 2.3.2 */ public static CharSequence readShaderSource(final Uri sourceLocation, final boolean mutableStringBuilder) throws IOException { final URLConnection conn = IOUtil.openURL(sourceLocation.toURL(), "ShaderCode "); if (conn == null) { return null; } final StringBuilder result = new StringBuilder(); readShaderSource(null, conn, result); return mutableStringBuilder ? result : result.toString(); } /** * Reads shader binary located in <code>path</code>, * either relative to the <code>context</code> class or absolute <i>as-is</i>. * <p> * Final location lookup is perfomed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, * see {@link IOUtil#getResource(Class, String)}. * </p> * * @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(final Class<?> context, final String path) throws IOException { final URLConnection conn = IOUtil.getResource(path, context.getClassLoader(), context); if (conn == null) { return null; } final BufferedInputStream bis = new BufferedInputStream( conn.getInputStream() ); try { return IOUtil.copyStream2ByteBuffer( bis ); } finally { IOUtil.close(bis, false); } } /** * Reads shader binary located from {@link Uri#absolute} {@link Uri} <code>binLocation</code>. * @param binLocation {@link Uri} location of shader binary * @throws IOException * @since 2.3.2 */ public static ByteBuffer readShaderBinary(final Uri binLocation) throws IOException { final URLConnection conn = IOUtil.openURL(binLocation.toURL(), "ShaderCode "); 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}, * same as for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}, i.e {@link #es3_default_precision_vp}, * due to ES 3.x requirements of using same precision for uniforms! */ public static final String es3_default_precision_fp = es3_default_precision_vp; /** 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"; /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String REQUIRE = "require"; /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String ENABLE = "enable"; /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String DISABLE = "disable"; /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */ public static final String WARN = "warn"; /** * Creates a GLSL extension directive. * <p> * Prefer {@link #ENABLE} over {@link #REQUIRE}, since the latter will force a failure if not supported. * </p> * * @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(final String extensionName, final String behavior) { return "#extension " + extensionName + " : " + behavior + "\n"; } /** * Add GLSL version at the head of this shader source code. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * @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(final 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. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * @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(final GL2ES2 gl, int pos) { final String defaultPrecision; 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; case GL3ES3.GL_COMPUTE_SHADER: defaultPrecision = es3_default_precision_fp; break; default: defaultPrecision = null; break; } } else 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( 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: case GL3.GL_TESS_CONTROL_SHADER: case GL3.GL_TESS_EVALUATION_SHADER: defaultPrecision = gl3_default_precision_vp_gp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = gl3_default_precision_fp; break; case GL3ES3.GL_COMPUTE_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(final 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(final GL2ES2 gl) { if( gl.isGL3() ) { final VersionNumber glslVersion = gl.getContext().getGLSLVersionNumber(); return glslVersion.compareTo(GLContext.Version1_30) >= 0 && glslVersion.compareTo(GLContext.Version1_50) < 0 ; } else { return false; } } /** * Default customization of this shader source code. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * @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 <code>true</code> 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(final GL2ES2 gl, final boolean preludeVersion, final 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. * <p> * Note: The shader source to be edit must be created using a mutable StringBuilder. * </p> * @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(final GL2ES2 gl, final boolean preludeVersion, final 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; }