aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java322
1 files changed, 303 insertions, 19 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 6052c3b49..e1db3f7a3 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java
@@ -189,7 +189,7 @@ public class ShaderCode {
* {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_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>
+ * @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)}.
@@ -200,7 +200,8 @@ public class ShaderCode {
* @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) {
+ 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;
}
@@ -227,6 +228,53 @@ public class ShaderCode {
}
/**
+ * 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} or {@link GL4#GL_TESS_EVALUATION_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)}.
*
@@ -259,6 +307,37 @@ public class ShaderCode {
}
/**
+ * 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} or {@link GL4#GL_TESS_EVALUATION_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>
@@ -327,7 +406,8 @@ public class ShaderCode {
* whatever is available first.
* <p>
* The source and binary location names are expected w/o suffixes which are
- * resolved and appended using {@link #getFileSuffix(boolean, int)}.
+ * 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>
@@ -358,9 +438,11 @@ public class ShaderCode {
* 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");
+ * "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" }, "shader/bin", "fragment");
+ * "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);
@@ -380,8 +462,12 @@ public class ShaderCode {
* @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)}.
@@ -397,9 +483,12 @@ public class ShaderCode {
* @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 binRoot, final String binBasename,
+ 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[];
@@ -408,7 +497,7 @@ public class ShaderCode {
if( null!=srcBasenames && ShaderUtil.isShaderCompilerAvailable(gl) ) {
srcPath = new String[srcBasenames.length];
- final String srcSuffix = getFileSuffix(false, type);
+ 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;
@@ -428,7 +517,7 @@ public class ShaderCode {
}
if( null!=binBasename ) {
final Set<Integer> binFmts = ShaderUtil.getShaderBinaryFormats(gl);
- final String binSuffix = getFileSuffix(true, type);
+ 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);
@@ -445,7 +534,162 @@ public class ShaderCode {
}
/**
- * Simplified variation of {@link #create(GL2ES2, int, int, Class, String, String[], String, String, boolean)}.
+ * 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} or {@link GL4#GL_TESS_EVALUATION_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} or {@link GL4#GL_TESS_EVALUATION_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>
@@ -469,9 +713,9 @@ public class ShaderCode {
* Your invocation in org/test/glsl/MyShaderTest.java:
*
* ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(),
- * "shader", "shader/bin", "vertex");
+ * "shader", "shader/bin", "vertex", true);
* ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(),
- * "shader", "shader/bin", "fragment");
+ * "shader", "shader/bin", "fragment", true);
* ShaderProgram sp0 = new ShaderProgram();
* sp0.add(gl, vp0, System.err);
* sp0.add(gl, fp0, System.err);
@@ -496,12 +740,11 @@ public class ShaderCode {
* 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)
*/
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 create(gl, type, 1, context, srcRoot, new String[] { basename }, binRoot, basename, mutableStringBuilder );
+ final String srcRoot, final String binRoot, final String basename,
+ final boolean mutableStringBuilder) {
+ return ShaderCode.create(gl, type, context, srcRoot, binRoot, basename, null, null, mutableStringBuilder);
}
/**
@@ -815,7 +1058,6 @@ public class ShaderCode {
}
}
- @SuppressWarnings("resource")
private static int readShaderSource(final Class<?> context, final URLConnection conn, final StringBuilder result, int lineno) throws IOException {
if(DEBUG_CODE) {
if(0 == lineno) {
@@ -858,10 +1100,11 @@ public class ShaderCode {
}
/**
+ * Reads shader source located in <code>conn</code>.
*
- * @param context
- * @param conn
- * @param result
+ * @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 {
@@ -899,6 +1142,28 @@ public class ShaderCode {
}
/**
+ * 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>
@@ -925,6 +1190,25 @@ public class ShaderCode {
}
}
+ /**
+ * 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";