diff options
author | Sven Gothel <[email protected]> | 2012-04-16 01:38:49 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2012-04-16 01:38:49 +0200 |
commit | 62e5686fb583ad991d5811baf242d40d21952e27 (patch) | |
tree | 6bf20b4f0422c1c6e5cdf7843d9ef4e03e08d83b /src/jogl/classes/com/jogamp/opengl/util | |
parent | 131c40a80427d5e35824ad41da375edd4792fb60 (diff) |
API Change ShaderCode/ShaderUtil: Enable optional mutable shader source / generalize shader source storage type to CharSequence[]
Benefits:
- Allows code injection and general shader source editing (before compilation)
- Uses mutable StringBuilder only if editing is intended, hence reduces memory footprint
and String conversion at compilation in such case.
- ShaderCode.create(..) factory methods add nw attribute '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
'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.
- New 'edit' methods in ShaderCode: '
- int insertShaderSource(int shaderIdx, String tag, int fromIndex, CharSequence data);
- int insertShaderSource(int shaderIdx, int position, CharSequence data);
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/util')
-rw-r--r-- | src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java | 253 | ||||
-rw-r--r-- | src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderUtil.java | 24 |
2 files changed, 214 insertions, 63 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java index 556345ee5..e347b4171 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java +++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java @@ -28,23 +28,36 @@ package com.jogamp.opengl.util.glsl; -import com.jogamp.common.nio.Buffers; -import com.jogamp.common.util.IOUtil; - -import javax.media.opengl.*; +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.GLES2; +import javax.media.opengl.GLException; import jogamp.opengl.Debug; -import java.util.*; -import java.nio.*; -import java.io.*; -import java.net.*; +import com.jogamp.common.nio.Buffers; +import com.jogamp.common.util.IOUtil; /** * 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, int, Class, String, String, String) here} and + * {@link #create(GL2ES2, int, Class, String, String, String, boolean) here} and * {@link #create(GL2ES2, int, int, Class, String, String[], String, String) here}. * </p> */ @@ -70,11 +83,12 @@ public class ShaderCode { /** * @param type either {@link GL2ES2#GL_VERTEX_SHADER} or {@link GL2ES2#GL_FRAGMENT_SHADER} * @param count number of shaders - * @param source string array containing the shader sources, organized as <code>source[count][strings-per-shader]</code> + * @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</count> and <code>source.length</code> do not match */ - public ShaderCode(int type, int count, String[][] source) { + 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."); } @@ -128,22 +142,28 @@ public class ShaderCode { * @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> + * @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</count> and <code>sourceFiles.length</code> do not match * @see #readShaderSource(Class, String) */ - public static ShaderCode create(GL2ES2 gl, int type, int count, Class<?> context, String[] sourceFiles) { + public static ShaderCode create(GL2ES2 gl, int type, int count, Class<?> context, String[] sourceFiles, boolean mutableStringBuilder) { if(null != gl && !ShaderUtil.isShaderCompilerAvailable(gl)) { return null; } - String[][] shaderSources = null; + CharSequence[][] shaderSources = null; if(null!=sourceFiles) { // sourceFiles.length and count is validated in ctor - shaderSources = new String[sourceFiles.length][1]; + shaderSources = new CharSequence[sourceFiles.length][1]; for(int i=0; i<sourceFiles.length; i++) { try { - shaderSources[i][0] = readShaderSource(context, sourceFiles[i]); + shaderSources[i][0] = readShaderSource(context, sourceFiles[i], mutableStringBuilder); } catch (IOException ioe) { throw new RuntimeException("readShaderSource("+sourceFiles[i]+") error: ", ioe); } @@ -204,7 +224,7 @@ public class ShaderCode { * * @throws GLException if <code>type</code> is not supported * - * @see #create(GL2ES2, int, int, Class, String, String, String) + * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getFileSuffix(boolean binary, int type) { switch (type) { @@ -225,7 +245,7 @@ public class ShaderCode { * * @throws GLException if <code>binFormat</code> is not supported * - * @see #create(GL2ES2, int, int, Class, String, String, String) + * @see #create(GL2ES2, int, Class, String, String, String, boolean) */ public static String getBinarySubPath(int binFormat) { switch (binFormat) { @@ -282,7 +302,7 @@ public class ShaderCode { * sp0.add(gl, fp0, System.err); * st.attachShaderProgram(gl, sp0, true); * </pre> - * A simplified entry point is {@link #create(GL2ES2, int, int, Class, String, String, String)}. + * 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)}. @@ -297,6 +317,12 @@ public class ShaderCode { * @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</count> and <code>srcBasenames.length</code> do not match * @@ -308,7 +334,8 @@ public class ShaderCode { * @see #getBinarySubPath(int) */ public static ShaderCode create(GL2ES2 gl, int type, int count, Class<?> context, - String srcRoot, String[] srcBasenames, String binRoot, String binBasename) { + String srcRoot, String[] srcBasenames, String binRoot, String binBasename, + boolean mutableStringBuilder) { ShaderCode res = null; final String srcPath[]; String srcPathString = null; @@ -320,7 +347,7 @@ public class ShaderCode { for(int i=0; i<srcPath.length; i++) { srcPath[i] = srcRoot + '/' + srcBasenames[i] + "." + srcSuffix; } - res = create(gl, type, count, context, srcPath); + res = create(gl, type, count, context, srcPath, mutableStringBuilder); if(null!=res) { return res; } @@ -371,9 +398,9 @@ public class ShaderCode { * * Your invocation in org/test/glsl/MyShaderTest.java: * - * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, 1, this.getClass(), + * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(), * "shader", "shader/bin", "vertex"); - * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(), + * 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); @@ -384,20 +411,25 @@ public class ShaderCode { * @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} or {@link GL2ES2#GL_FRAGMENT_SHADER} - * @param count number of shaders, must be one * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>basename</code> * @param binRoot relative <i>root</i> path for <code>basename</code> + * @param mutableStringBuilder TODO * @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</count> is not 1 * * @see #create(GL2ES2, int, int, Class, String, String[], String, String) */ - public static ShaderCode create(GL2ES2 gl, int type, int count, Class<?> context, - String srcRoot, String binRoot, String basename) { - return create(gl, type, count, context, srcRoot, new String[] { basename }, binRoot, basename ); + 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 ); } /** @@ -418,9 +450,9 @@ public class ShaderCode { return "UNKNOWN_SHADER"; } - public int shaderBinaryFormat() { return shaderBinaryFormat; } - public Buffer shaderBinary() { return shaderBinary; } - public String[][] shaderSource() { return shaderSource; } + public int shaderBinaryFormat() { return shaderBinaryFormat; } + public Buffer shaderBinary() { return shaderBinary; } + public CharSequence[][] shaderSource() { return shaderSource; } public boolean isValid() { return valid; } @@ -434,6 +466,9 @@ public class ShaderCode { // Create & Compile the vertex/fragment shader objects if(null!=shaderSource) { + if(DEBUG_CODE) { + dumpShaderSource(System.err); + } valid=ShaderUtil.createAndCompileShader(gl, shader, shaderType, shaderSource, verboseOut); } else if(null!=shaderBinary) { @@ -473,7 +508,7 @@ public class ShaderCode { return id; } public String toString() { - StringBuffer buf = new StringBuffer("ShaderCode[id="+id+", type="+shaderTypeStr()+", valid="+valid+", shader: "); + 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)); } @@ -490,38 +525,140 @@ public class ShaderCode { out.println("<no shader source>"); return; } - int sourceNum = (null!=shaderSource)?shaderSource.length:0; - int shaderNum = (null!=shader)?shader.capacity():0; - for(int i=0; i<shaderNum; i++) { + final int sourceCount = (null!=shaderSource)?shaderSource.length:0; + final int shaderCount = (null!=shader)?shader.capacity():0; + for(int i=0; i<shaderCount; i++) { out.println(""); - out.println("Shader #"+i+"/"+shaderNum+" name "+shader.get(i)); + out.println("Shader #"+i+"/"+shaderCount+" name "+shader.get(i)); out.println("--------------------------------------------------------------"); - if(i>=sourceNum) { + if(i>=sourceCount) { out.println("<no shader source>"); } else { - String[] src = shaderSource[i]; + CharSequence[] src = shaderSource[i]; + int lineno=0; + for(int j=0; j<src.length; j++) { - out.println("Segment "+j+"/"+src.length+" :"); - out.println(src[j]); - out.println(""); + 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 (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> + */ + 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; 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; + } - private static int readShaderSource(Class<?> context, URLConnection conn, StringBuffer result, int lineno) throws IOException { + /** + * Adds <code>data</code> at <code>offset</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> + * @param data the text to be inserted. Shall end with an EOL '\n' character. + * @return index after the inserted <code>data</code> + */ + 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<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(position < curEndIndex) { + sb.insert(position, data); + return position+data.length(); + } + } + return -1; + } + + private static int readShaderSource(Class<?> context, URLConnection conn, StringBuilder result, int lineno) throws IOException { if(DEBUG_CODE) { - System.err.printf("%3d: // %s\n", lineno, conn.getURL()); + 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(DEBUG_CODE) { - System.err.printf("%3d: %s\n", lineno, line); - } if (line.startsWith("#include ")) { String includeFile = line.substring(9).trim(); URLConnection nextConn = null; @@ -554,40 +691,38 @@ public class ShaderCode { * @param result * @throws IOException */ - public static void readShaderSource(Class<?> context, URLConnection conn, StringBuffer result) throws IOException { - if(DEBUG_CODE) { - System.err.println(); - System.err.println("// -----------------------------------------------------------"); - } + public static void readShaderSource(Class<?> context, URLConnection conn, StringBuilder result) throws IOException { readShaderSource(context, conn, result, 0); - if(DEBUG_CODE) { - System.err.println("// -----------------------------------------------------------"); - System.err.println(); - } } /** * 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 perfomed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)}, + * 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 String readShaderSource(Class<?> context, String path) throws IOException { + public static CharSequence readShaderSource(Class<?> context, String path, boolean mutableStringBuilder) throws IOException { URLConnection conn = IOUtil.getResource(context, path); if (conn == null) { return null; } - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); readShaderSource(context, conn, result); - return result.toString(); + return mutableStringBuilder ? result : result.toString(); } /** @@ -621,7 +756,7 @@ public class ShaderCode { // Internals only below this point // - protected String[][] shaderSource = null; + protected CharSequence[][] shaderSource = null; protected Buffer shaderBinary = null; protected int shaderBinaryFormat = -1; protected IntBuffer shader = null; diff --git a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderUtil.java b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderUtil.java index d8aa1aa7f..40c052498 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderUtil.java @@ -200,7 +200,7 @@ public class ShaderUtil { return info.shaderCompilerAvailable.booleanValue(); } - public static void shaderSource(GL _gl, int shader, java.lang.String[] source) + public static void shaderSource(GL _gl, int shader, CharSequence[] source) { final GL2ES2 gl = _gl.getGL2ES2(); if(!isShaderCompilerAvailable(_gl)) { @@ -215,11 +215,27 @@ public class ShaderUtil { IntBuffer lengths = Buffers.newDirectIntBuffer(count); for(int i=0; i<count; i++) { lengths.put(i, source[i].length()); + } + if(source instanceof String[]) { + // rare case .. + gl.glShaderSource(shader, count, (String[])source, lengths); + } else { + final String[] tmp = new String[source.length]; + for(int i = source.length - 1; i>=0; i--) { + final CharSequence csq = source[i]; + if(csq instanceof String) { + // if ShaderCode.create(.. mutableStringBuffer == false ) + tmp[i] = (String) csq; + } else { + // if ShaderCode.create(.. mutableStringBuffer == true ) + tmp[i] = source[i].toString(); + } + } + gl.glShaderSource(shader, count, tmp, lengths); } - gl.glShaderSource(shader, count, source, lengths); } - public static void shaderSource(GL _gl, IntBuffer shaders, java.lang.String[][] sources) + public static void shaderSource(GL _gl, IntBuffer shaders, CharSequence[][] sources) { int sourceNum = (null!=sources)?sources.length:0; int shaderNum = (null!=shaders)?shaders.remaining():0; @@ -312,7 +328,7 @@ public class ShaderUtil { } public static boolean createAndCompileShader(GL _gl, IntBuffer shader, int shaderType, - java.lang.String[][] sources, + CharSequence[][] sources, PrintStream verboseOut) { final GL2ES2 gl = _gl.getGL2ES2(); |