From 243bba48ffeebd693acea3ff09da8b3cc086e2a6 Mon Sep 17 00:00:00 2001 From: "Sha..rd" Date: Wed, 19 Sep 2012 01:00:33 +0000 Subject: [PATCH] * Improvements to GLSL loader handling of libraries. The order of #import is maintained, it is also possible to #import in the body of the shader (but it will not be included twice). Fixed issue 523. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9752 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../com/jme3/shader/plugins/GLSLLoader.java | 212 ++++++++---------- .../shader/plugins/ShaderDependencyNode.java | 72 ++++++ 2 files changed, 166 insertions(+), 118 deletions(-) create mode 100644 engine/src/core-plugins/com/jme3/shader/plugins/ShaderDependencyNode.java diff --git a/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java index 536e55f39..224cf3ad0 100644 --- a/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java +++ b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java @@ -34,13 +34,17 @@ package com.jme3.shader.plugins; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetManager; import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.system.JmeSystem; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.Reader; import java.util.*; /** @@ -48,39 +52,16 @@ import java.util.*; */ public class GLSLLoader implements AssetLoader { - private AssetManager owner; - private Map dependCache = new HashMap(); + private AssetManager assetManager; + private Map dependCache = new HashMap(); - private class DependencyNode { - - private String shaderSource; - private String shaderName; - - private final Set dependsOn = new HashSet(); - private final Set dependOnMe = new HashSet(); - - public DependencyNode(String shaderName){ - this.shaderName = shaderName; - } - - public void setSource(String source){ - this.shaderSource = source; - } - - public void addDependency(DependencyNode node){ - if (this.dependsOn.contains(node)) - return; // already contains dependency - -// System.out.println(shaderName + " depend on "+node.shaderName); - this.dependsOn.add(node); - node.dependOnMe.add(this); - } - - } - - private class GlslDependKey extends AssetKey { + /** + * Used to load {@link ShaderDependencyNode}s. + * Asset caching is disabled. + */ + private class ShaderDependencyKey extends AssetKey { - public GlslDependKey(String name) { + public ShaderDependencyKey(String name) { super(name); } @@ -91,124 +72,119 @@ public class GLSLLoader implements AssetLoader { } } - private DependencyNode loadNode(InputStream in, String nodeName) throws IOException{ - DependencyNode node = new DependencyNode(nodeName); - if (in == null) - throw new IOException("Dependency "+nodeName+" cannot be found."); + /** + * Creates a {@link ShaderDependencyNode} from a stream representing shader code. + * + * @param in The input stream containing shader code + * @param nodeName + * @return + * @throws IOException + */ + private ShaderDependencyNode loadNode(Reader reader, String nodeName) { + ShaderDependencyNode node = new ShaderDependencyNode(nodeName); StringBuilder sb = new StringBuilder(); - BufferedReader r = new BufferedReader(new InputStreamReader(in)); - while (r.ready()){ - String ln = r.readLine(); - if (ln.startsWith("#import ")){ - ln = ln.substring(8).trim(); - if (ln.startsWith("\"") && ln.endsWith("\"") && ln.length() > 3){ - // import user code - // remove quotes to get filename - ln = ln.substring(1, ln.length()-1); - if (ln.equals(nodeName)) - throw new IOException("Node depends on itself."); - - // check cache first - DependencyNode dependNode = dependCache.get(ln); - if (dependNode == null){ - GlslDependKey key = new GlslDependKey(ln); - // make sure not to register an input stream with - // the cache.. - InputStream stream = (InputStream) owner.loadAsset(key); - dependNode = loadNode(stream, ln); + BufferedReader bufReader = new BufferedReader(reader); + try { + while (bufReader.ready()) { + String ln = bufReader.readLine(); + if (ln.trim().startsWith("#import ")) { + ln = ln.trim().substring(8).trim(); + if (ln.startsWith("\"") && ln.endsWith("\"") && ln.length() > 3) { + // import user code + // remove quotes to get filename + ln = ln.substring(1, ln.length() - 1); + if (ln.equals(nodeName)) { + throw new IOException("Node depends on itself."); + } + + // check cache first + ShaderDependencyNode dependNode = dependCache.get(ln); + + if (dependNode == null) { + Reader dependNodeReader = assetManager.loadAsset(new ShaderDependencyKey(ln)); + dependNode = loadNode(dependNodeReader, ln); + } + + node.addDependency(sb.length(), dependNode); } - node.addDependency(dependNode); + } else { + sb.append(ln).append('\n'); } -// }else if (ln.startsWith("uniform") || ln.startsWith("varying") || ln.startsWith("attribute")){ -// // these variables are included as dependencies as well -// DependencyNode dependNode = dependCache.get(ln); -// if (dependNode == null){ -// // the source and name are the same for variable dependencies -// dependNode = new DependencyNode(ln); -// dependNode.setSource(ln); -// dependCache.put(ln, dependNode); -// } -// node.addDependency(dependNode); - }else{ - sb.append(ln).append('\n'); } + } catch (IOException ex) { + if (bufReader != null) { + try { + bufReader.close(); + } catch (IOException ex1) { + } + } + throw new AssetLoadException("Failed to load shader node: " + nodeName, ex); } - r.close(); node.setSource(sb.toString()); dependCache.put(nodeName, node); return node; } - private DependencyNode nextIndependentNode(List checkedNodes){ - Collection allNodes = dependCache.values(); - if (allNodes == null || allNodes.isEmpty()) + private ShaderDependencyNode nextIndependentNode() throws IOException { + Collection allNodes = dependCache.values(); + + if (allNodes == null || allNodes.isEmpty()) { return null; + } - for (DependencyNode node : allNodes){ - if (node.dependsOn.isEmpty()){ + for (ShaderDependencyNode node : allNodes) { + if (node.getDependOnMe().isEmpty()) { return node; } } - // circular dependency found.. - for (DependencyNode node : allNodes){ - System.out.println(node.shaderName); + // Circular dependency found.. + for (ShaderDependencyNode node : allNodes){ + System.out.println(node.getName()); } - throw new RuntimeException("Circular dependency."); + + throw new IOException("Circular dependency."); } - - private String resolveDependencies(DependencyNode root){ - StringBuilder sb = new StringBuilder(); - - List checkedNodes = new ArrayList(); - checkedNodes.add(root); - while (true){ - DependencyNode indepnNode = nextIndependentNode(checkedNodes); - if (indepnNode == null) - break; - - sb.append(indepnNode.shaderSource).append('\n'); - dependCache.remove(indepnNode.shaderName); - - // take out this dependency - for (Iterator iter = indepnNode.dependOnMe.iterator(); - iter.hasNext();){ - DependencyNode dependNode = iter.next(); - iter.remove(); - dependNode.dependsOn.remove(indepnNode); + + private String resolveDependencies(ShaderDependencyNode node, Set alreadyInjectedSet) { + if (alreadyInjectedSet.contains(node)) { + return "// " + node.getName() + " was already injected at the top.\n"; + } else { + alreadyInjectedSet.add(node); + } + if (node.getDependencies().isEmpty()) { + return node.getSource(); + } else { + StringBuilder sb = new StringBuilder(node.getSource()); + List resolvedShaderNodes = new ArrayList(); + for (ShaderDependencyNode dependencyNode : node.getDependencies()) { + resolvedShaderNodes.add( resolveDependencies(dependencyNode, alreadyInjectedSet) ); + } + List injectIndices = node.getDependencyInjectIndices(); + for (int i = resolvedShaderNodes.size() - 1; i >= 0; i--) { + // Must insert them backwards .. + sb.insert(injectIndices.get(i), resolvedShaderNodes.get(i)); } + return sb.toString(); } - -// System.out.println(sb.toString()); -// System.out.println("--------------------------------------------------"); - - return sb.toString(); } - /** - * - * @param info - * @return String of GLSL code - * @throws java.io.IOException - */ public Object load(AssetInfo info) throws IOException { // The input stream provided is for the vertex shader, // to retrieve the fragment shader, use the content manager - this.owner = info.getManager(); - if (info.getKey().getExtension().equals("glsllib")){ + this.assetManager = info.getManager(); + Reader reader = new InputStreamReader(info.openStream()); + if (info.getKey().getExtension().equals("glsllib")) { // NOTE: Loopback, GLSLLIB is loaded by this loader // and needs data as InputStream - return info.openStream(); - }else{ - // GLSLLoader wants result as String for - // fragment shader - DependencyNode rootNode = loadNode(info.openStream(), "[main]"); - String code = resolveDependencies(rootNode); + return reader; + } else { + ShaderDependencyNode rootNode = loadNode(reader, "[main]"); + String code = resolveDependencies(rootNode, new HashSet()); dependCache.clear(); return code; } } - } diff --git a/engine/src/core-plugins/com/jme3/shader/plugins/ShaderDependencyNode.java b/engine/src/core-plugins/com/jme3/shader/plugins/ShaderDependencyNode.java new file mode 100644 index 000000000..0c3d3309d --- /dev/null +++ b/engine/src/core-plugins/com/jme3/shader/plugins/ShaderDependencyNode.java @@ -0,0 +1,72 @@ +package com.jme3.shader.plugins; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class ShaderDependencyNode { + + private String shaderSource; + private String shaderName; + + private final List dependencies = new ArrayList(); + private final List dependencyInjectIndices = new ArrayList(); + private final List dependOnMe = new ArrayList(); + + public ShaderDependencyNode(String shaderName){ + this.shaderName = shaderName; + } + + public String getSource() { + return shaderSource; + } + + public void setSource(String shaderSource) { + this.shaderSource = shaderSource; + } + + public String getName() { + return shaderName; + } + + public void setName(String shaderName) { + this.shaderName = shaderName; + } + + public void addDependency(int index, ShaderDependencyNode node){ + if (this.dependencies.contains(node)) { + // already contains dependency .. + return; + } + + this.dependencies.add(node); + this.dependencyInjectIndices.add(index); + node.dependOnMe.add(this); + } + + public void removeDependency(ShaderDependencyNode node) { + int positionInList = this.dependencies.indexOf(node); + if (positionInList == -1) { + throw new IllegalArgumentException("The given node " + + node.getName() + + " is not in this node's (" + + getName() + + ") dependency list"); + } + + this.dependencies.remove(positionInList); + this.dependencyInjectIndices.remove(positionInList); + } + + public List getDependOnMe() { + return Collections.unmodifiableList(dependOnMe); + } + + public List getDependencies() { + return Collections.unmodifiableList(dependencies); + } + + public List getDependencyInjectIndices() { + return Collections.unmodifiableList(dependencyInjectIndices); + } +}