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
33package com.jme3.shader.plugins;
34
35import com.jme3.asset.AssetInfo;
36import com.jme3.asset.AssetKey;
37import com.jme3.asset.AssetLoader;
38import com.jme3.asset.AssetManager;
39import java.io.BufferedReader;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.InputStreamReader;
43import java.util.*;
44
45/**
46 * GLSL File parser that supports #import pre-processor statement
47 */
48public 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
61        public DependencyNode(String shaderName){
62            this.shaderName = shaderName;
63        }
64
65        public void setSource(String source){
66            this.shaderSource = source;
67        }
68
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> {
81        public GlslDependKey(String name){
82            super(name);
83        }
84        @Override
85        public boolean shouldCache(){
86            return false;
87        }
88    }
89
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
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
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     */
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