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;
34
35import com.jme3.export.*;
36import com.jme3.renderer.Renderer;
37import com.jme3.scene.VertexBuffer;
38import com.jme3.util.IntMap;
39import com.jme3.util.IntMap.Entry;
40import com.jme3.util.ListMap;
41import com.jme3.util.NativeObject;
42import java.io.IOException;
43import java.util.ArrayList;
44import java.util.Collection;
45import java.util.HashMap;
46
47public final class Shader extends NativeObject implements Savable {
48
49    private String language;
50
51    /**
52     * True if the shader is fully compiled & linked.
53     * (e.g no GL error will be invoked if used).
54     */
55    private boolean usable = false;
56
57    /**
58     * A list of all shaders currently attached.
59     */
60    private ArrayList<ShaderSource> shaderList;
61
62    /**
63     * Maps uniform name to the uniform variable.
64     */
65//    private HashMap<String, Uniform> uniforms;
66    private ListMap<String, Uniform> uniforms;
67
68    /**
69     * Maps attribute name to the location of the attribute in the shader.
70     */
71    private IntMap<Attribute> attribs;
72
73    /**
74     * Type of shader. The shader will control the pipeline of it's type.
75     */
76    public static enum ShaderType {
77        /**
78         * Control fragment rasterization. (e.g color of pixel).
79         */
80        Fragment,
81
82        /**
83         * Control vertex processing. (e.g transform of model to clip space)
84         */
85        Vertex,
86
87        /**
88         * Control geometry assembly. (e.g compile a triangle list from input data)
89         */
90        Geometry;
91    }
92
93    /**
94     * Shader source describes a shader object in OpenGL. Each shader source
95     * is assigned a certain pipeline which it controls (described by it's type).
96     */
97    public static class ShaderSource extends NativeObject implements Savable {
98
99        ShaderType shaderType;
100
101        boolean usable = false;
102        String name = null;
103        String source = null;
104        String defines = null;
105
106        public ShaderSource(ShaderType type){
107            super(ShaderSource.class);
108            this.shaderType = type;
109            if (type == null)
110                throw new NullPointerException("The shader type must be specified");
111        }
112
113        protected ShaderSource(ShaderSource ss){
114            super(ShaderSource.class, ss.id);
115            this.shaderType = ss.shaderType;
116            usable = false;
117            name = ss.name;
118            // forget source & defines
119        }
120
121        public ShaderSource(){
122            super(ShaderSource.class);
123        }
124
125        public void write(JmeExporter ex) throws IOException{
126            OutputCapsule oc = ex.getCapsule(this);
127            oc.write(shaderType, "shaderType", null);
128            oc.write(name, "name", null);
129            oc.write(source, "source", null);
130            oc.write(defines, "defines", null);
131        }
132
133        public void read(JmeImporter im) throws IOException{
134            InputCapsule ic = im.getCapsule(this);
135            shaderType = ic.readEnum("shaderType", ShaderType.class, null);
136            name = ic.readString("name", null);
137            source = ic.readString("source", null);
138            defines = ic.readString("defines", null);
139        }
140
141        public void setName(String name){
142            this.name = name;
143        }
144
145        public String getName(){
146            return name;
147        }
148
149        public ShaderType getType() {
150            return shaderType;
151        }
152
153        public void setSource(String source){
154            if (source == null)
155                throw new NullPointerException("Shader source cannot be null");
156
157            this.source = source;
158            setUpdateNeeded();
159        }
160
161        public void setDefines(String defines){
162            if (defines == null)
163                throw new NullPointerException("Shader defines cannot be null");
164
165            this.defines = defines;
166            setUpdateNeeded();
167        }
168
169        public String getSource(){
170            return source;
171        }
172
173        public String getDefines(){
174            return defines;
175        }
176
177        public boolean isUsable(){
178            return usable;
179        }
180
181        public void setUsable(boolean usable){
182            this.usable = usable;
183        }
184
185        @Override
186        public String toString(){
187            String nameTxt = "";
188            if (name != null)
189                nameTxt = "name="+name+", ";
190            if (defines != null)
191                nameTxt += "defines, ";
192
193
194            return getClass().getSimpleName() + "["+nameTxt+"type="
195                                              + shaderType.name()+"]";
196        }
197
198        public void resetObject(){
199            id = -1;
200            usable = false;
201            setUpdateNeeded();
202        }
203
204        public void deleteObject(Object rendererObject){
205            ((Renderer)rendererObject).deleteShaderSource(ShaderSource.this);
206        }
207
208        public NativeObject createDestructableClone(){
209            return new ShaderSource(ShaderSource.this);
210        }
211    }
212
213    /**
214     * Create an empty shader.
215     */
216    public Shader(String language){
217        super(Shader.class);
218        this.language = language;
219        shaderList = new ArrayList<ShaderSource>();
220//        uniforms = new HashMap<String, Uniform>();
221        uniforms = new ListMap<String, Uniform>();
222        attribs = new IntMap<Attribute>();
223    }
224
225    /**
226     * Do not use this constructor. Serialization purposes only.
227     */
228    public Shader(){
229        super(Shader.class);
230    }
231
232    protected Shader(Shader s){
233        super(Shader.class, s.id);
234        shaderList = new ArrayList<ShaderSource>();
235        //uniforms = new ListMap<String, Uniform>();
236        //attribs = new IntMap<Attribute>();
237
238        // NOTE: Because ShaderSources are registered separately with
239        // the GLObjectManager
240        for (ShaderSource source : s.shaderList){
241            shaderList.add( (ShaderSource)source.createDestructableClone() );
242        }
243    }
244
245    public void write(JmeExporter ex) throws IOException{
246        OutputCapsule oc = ex.getCapsule(this);
247        oc.write(language, "language", null);
248        oc.writeSavableArrayList(shaderList, "shaderList", null);
249        oc.writeIntSavableMap(attribs, "attribs", null);
250        oc.writeStringSavableMap(uniforms, "uniforms", null);
251    }
252
253    public void read(JmeImporter im) throws IOException{
254        InputCapsule ic = im.getCapsule(this);
255        language = ic.readString("language", null);
256        shaderList = ic.readSavableArrayList("shaderList", null);
257        attribs = (IntMap<Attribute>) ic.readIntSavableMap("attribs", null);
258
259        HashMap<String, Uniform> uniMap = (HashMap<String, Uniform>) ic.readStringSavableMap("uniforms", null);
260        uniforms = new ListMap<String, Uniform>(uniMap);
261    }
262
263    /**
264     * Creates a deep clone of the shader, where the sources are available
265     * but have not been compiled yet. Does not copy the uniforms or attribs.
266     * @return
267     */
268//    public Shader createDeepClone(String defines){
269//        Shader newShader = new Shader(language);
270//        for (ShaderSource source : shaderList){
271//            if (!source.getDefines().equals(defines)){
272//                // need to clone the shadersource so
273//                // the correct defines can be placed
274//                ShaderSource newSource = new ShaderSource(source.getType());
275//                newSource.setSource(source.getSource());
276//                newSource.setDefines(defines);
277//                newShader.addSource(newSource);
278//            }else{
279//                // no need to clone source, also saves
280//                // having to compile the shadersource
281//                newShader.addSource(source);
282//            }
283//        }
284//        return newShader;
285//    }
286
287    /**
288     * Adds source code to a certain pipeline.
289     *
290     * @param type The pipeline to control
291     * @param source The shader source code (in GLSL).
292     */
293    public void addSource(ShaderType type, String name, String source, String defines){
294        ShaderSource shader = new ShaderSource(type);
295        shader.setSource(source);
296        shader.setName(name);
297        if (defines != null)
298            shader.setDefines(defines);
299
300        shaderList.add(shader);
301        setUpdateNeeded();
302    }
303
304    public void addSource(ShaderType type, String source, String defines){
305        addSource(type, null, source, defines);
306    }
307
308    public void addSource(ShaderType type, String source){
309        addSource(type, source, null);
310    }
311
312    /**
313     * Adds an existing shader source to this shader.
314     * @param source
315     */
316    private void addSource(ShaderSource source){
317        shaderList.add(source);
318        setUpdateNeeded();
319    }
320
321    public Uniform getUniform(String name){
322        Uniform uniform = uniforms.get(name);
323        if (uniform == null){
324            uniform = new Uniform();
325            uniform.name = name;
326            uniforms.put(name, uniform);
327        }
328        return uniform;
329    }
330
331    public void removeUniform(String name){
332        uniforms.remove(name);
333    }
334
335    public Attribute getAttribute(VertexBuffer.Type attribType){
336        int ordinal = attribType.ordinal();
337        Attribute attrib = attribs.get(ordinal);
338        if (attrib == null){
339            attrib = new Attribute();
340            attrib.name = attribType.name();
341            attribs.put(ordinal, attrib);
342        }
343        return attrib;
344    }
345
346//    public Collection<Uniform> getUniforms(){
347//        return uniforms.values();
348//    }
349
350    public ListMap<String, Uniform> getUniformMap(){
351        return uniforms;
352    }
353
354//    public Collection<Attribute> getAttributes() {
355//        return attribs.
356//    }
357
358    public Collection<ShaderSource> getSources(){
359        return shaderList;
360    }
361
362    public String getLanguage(){
363        return language;
364    }
365
366    @Override
367    public String toString(){
368        return getClass().getSimpleName() + "[language="+language
369                                           + ", numSources="+shaderList.size()
370                                           + ", numUniforms="+uniforms.size()
371                                           + ", shaderSources="+getSources()+"]";
372    }
373
374    /**
375     * Clears all sources. Assuming that they have already been detached and
376     * removed on the GL side.
377     */
378    public void resetSources(){
379        shaderList.clear();
380    }
381
382    /**
383     * Returns true if this program and all it's shaders have been compiled,
384     * linked and validated successfuly.
385     */
386    public boolean isUsable(){
387        return usable;
388    }
389
390    /**
391     * Sets if the program can be used. Should only be called by the Renderer.
392     * @param usable
393     */
394    public void setUsable(boolean usable){
395        this.usable = usable;
396    }
397
398    /**
399     * Usually called when the shader itself changes or during any
400     * time when the var locations need to be refreshed.
401     */
402    public void resetLocations(){
403        // NOTE: Shader sources will be reset seperately from the shader itself.
404        for (Uniform uniform : uniforms.values()){
405            uniform.reset(); // fixes issue with re-initialization
406        }
407        for (Entry<Attribute> entry : attribs){
408            entry.getValue().location = -2;
409        }
410    }
411
412    @Override
413    public void setUpdateNeeded(){
414        super.setUpdateNeeded();
415        resetLocations();
416    }
417
418    /**
419     * Called by the object manager to reset all object IDs. This causes
420     * the shader to be reuploaded to the GPU incase the display was restarted.
421     */
422    @Override
423    public void resetObject() {
424        this.id = -1;
425        this.usable = false;
426
427        for (ShaderSource source : shaderList){
428            source.resetObject();
429        }
430
431        setUpdateNeeded();
432    }
433
434    @Override
435    public void deleteObject(Object rendererObject) {
436        ((Renderer)rendererObject).deleteShader(this);
437    }
438
439    public NativeObject createDestructableClone(){
440        return new Shader(this);
441    }
442
443}
444