1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.shader.plugins; 34 35 import com.jme3.asset.AssetInfo; 36 import com.jme3.asset.AssetKey; 37 import com.jme3.asset.AssetLoader; 38 import com.jme3.asset.AssetManager; 39 import java.io.BufferedReader; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.InputStreamReader; 43 import java.util.*; 44 45 /** 46 * GLSL File parser that supports #import pre-processor statement 47 */ 48 public class GLSLLoader implements AssetLoader { 49 50 private AssetManager owner; 51 private Map<String, DependencyNode> dependCache = new HashMap<String, DependencyNode>(); 52 53 private class DependencyNode { 54 55 private String shaderSource; 56 private String shaderName; 57 58 private final Set<DependencyNode> dependsOn = new HashSet<DependencyNode>(); 59 private final Set<DependencyNode> dependOnMe = new HashSet<DependencyNode>(); 60 DependencyNode(String shaderName)61 public DependencyNode(String shaderName){ 62 this.shaderName = shaderName; 63 } 64 setSource(String source)65 public void setSource(String source){ 66 this.shaderSource = source; 67 } 68 addDependency(DependencyNode node)69 public void addDependency(DependencyNode node){ 70 if (this.dependsOn.contains(node)) 71 return; // already contains dependency 72 73 // System.out.println(shaderName + " depend on "+node.shaderName); 74 this.dependsOn.add(node); 75 node.dependOnMe.add(this); 76 } 77 78 } 79 80 private class GlslDependKey extends AssetKey<InputStream> { GlslDependKey(String name)81 public GlslDependKey(String name){ 82 super(name); 83 } 84 @Override shouldCache()85 public boolean shouldCache(){ 86 return false; 87 } 88 } 89 loadNode(InputStream in, String nodeName)90 private DependencyNode loadNode(InputStream in, String nodeName) throws IOException{ 91 DependencyNode node = new DependencyNode(nodeName); 92 if (in == null) 93 throw new IOException("Dependency "+nodeName+" cannot be found."); 94 95 StringBuilder sb = new StringBuilder(); 96 BufferedReader r = new BufferedReader(new InputStreamReader(in)); 97 while (r.ready()){ 98 String ln = r.readLine(); 99 if (ln.startsWith("#import ")){ 100 ln = ln.substring(8).trim(); 101 if (ln.startsWith("\"") && ln.endsWith("\"") && ln.length() > 3){ 102 // import user code 103 // remove quotes to get filename 104 ln = ln.substring(1, ln.length()-1); 105 if (ln.equals(nodeName)) 106 throw new IOException("Node depends on itself."); 107 108 // check cache first 109 DependencyNode dependNode = dependCache.get(ln); 110 if (dependNode == null){ 111 GlslDependKey key = new GlslDependKey(ln); 112 // make sure not to register an input stream with 113 // the cache.. 114 InputStream stream = (InputStream) owner.loadAsset(key); 115 dependNode = loadNode(stream, ln); 116 } 117 node.addDependency(dependNode); 118 } 119 // }else if (ln.startsWith("uniform") || ln.startsWith("varying") || ln.startsWith("attribute")){ 120 // // these variables are included as dependencies as well 121 // DependencyNode dependNode = dependCache.get(ln); 122 // if (dependNode == null){ 123 // // the source and name are the same for variable dependencies 124 // dependNode = new DependencyNode(ln); 125 // dependNode.setSource(ln); 126 // dependCache.put(ln, dependNode); 127 // } 128 // node.addDependency(dependNode); 129 }else{ 130 sb.append(ln).append('\n'); 131 } 132 } 133 r.close(); 134 135 node.setSource(sb.toString()); 136 dependCache.put(nodeName, node); 137 return node; 138 } 139 nextIndependentNode(List<DependencyNode> checkedNodes)140 private DependencyNode nextIndependentNode(List<DependencyNode> checkedNodes){ 141 Collection<DependencyNode> allNodes = dependCache.values(); 142 if (allNodes == null || allNodes.isEmpty()) 143 return null; 144 145 for (DependencyNode node : allNodes){ 146 if (node.dependsOn.isEmpty()){ 147 return node; 148 } 149 } 150 151 // circular dependency found.. 152 for (DependencyNode node : allNodes){ 153 System.out.println(node.shaderName); 154 } 155 throw new RuntimeException("Circular dependency."); 156 } 157 resolveDependencies(DependencyNode root)158 private String resolveDependencies(DependencyNode root){ 159 StringBuilder sb = new StringBuilder(); 160 161 List<DependencyNode> checkedNodes = new ArrayList<DependencyNode>(); 162 checkedNodes.add(root); 163 while (true){ 164 DependencyNode indepnNode = nextIndependentNode(checkedNodes); 165 if (indepnNode == null) 166 break; 167 168 sb.append(indepnNode.shaderSource).append('\n'); 169 dependCache.remove(indepnNode.shaderName); 170 171 // take out this dependency 172 for (Iterator<DependencyNode> iter = indepnNode.dependOnMe.iterator(); 173 iter.hasNext();){ 174 DependencyNode dependNode = iter.next(); 175 iter.remove(); 176 dependNode.dependsOn.remove(indepnNode); 177 } 178 } 179 180 // System.out.println(sb.toString()); 181 // System.out.println("--------------------------------------------------"); 182 183 return sb.toString(); 184 } 185 186 /** 187 * 188 * @param owner 189 * @param in 190 * @param extension 191 * @param key 192 * @return 193 * @throws java.io.IOException 194 */ load(AssetInfo info)195 public Object load(AssetInfo info) throws IOException { 196 // The input stream provided is for the vertex shader, 197 // to retrieve the fragment shader, use the content manager 198 this.owner = info.getManager(); 199 if (info.getKey().getExtension().equals("glsllib")){ 200 // NOTE: Loopback, GLSLLIB is loaded by this loader 201 // and needs data as InputStream 202 return info.openStream(); 203 }else{ 204 // GLSLLoader wants result as String for 205 // fragment shader 206 DependencyNode rootNode = loadNode(info.openStream(), "[main]"); 207 String code = resolveDependencies(rootNode); 208 dependCache.clear(); 209 return code; 210 } 211 } 212 213 } 214