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 */
32package com.jme3.material;
33
34import com.jme3.asset.AssetManager;
35import com.jme3.export.*;
36import com.jme3.shader.*;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.Collection;
40import java.util.List;
41import java.util.logging.Logger;
42
43/**
44 * Represents a technique instance.
45 */
46public class Technique implements Savable {
47
48    private static final Logger logger = Logger.getLogger(Technique.class.getName());
49    private TechniqueDef def;
50    private Material owner;
51    private ArrayList<Uniform> worldBindUniforms;
52    private DefineList defines;
53    private Shader shader;
54    private boolean needReload = true;
55
56    /**
57     * Creates a new technique instance that implements the given
58     * technique definition.
59     *
60     * @param owner The material that will own this technique
61     * @param def The technique definition being implemented.
62     */
63    public Technique(Material owner, TechniqueDef def) {
64        this.owner = owner;
65        this.def = def;
66        if (def.isUsingShaders()) {
67            this.worldBindUniforms = new ArrayList<Uniform>();
68            this.defines = new DefineList();
69        }
70    }
71
72    /**
73     * Serialization only. Do not use.
74     */
75    public Technique() {
76    }
77
78    /**
79     * Returns the technique definition that is implemented by this technique
80     * instance.
81     *
82     * @return the technique definition that is implemented by this technique
83     * instance.
84     */
85    public TechniqueDef getDef() {
86        return def;
87    }
88
89    /**
90     * Returns the shader currently used by this technique instance.
91     * <p>
92     * Shaders are typically loaded dynamically when the technique is first
93     * used, therefore, this variable will most likely be null most of the time.
94     *
95     * @return the shader currently used by this technique instance.
96     */
97    public Shader getShader() {
98        return shader;
99    }
100
101    /**
102     * Returns a list of uniforms that implements the world parameters
103     * that were requested by the material definition.
104     *
105     * @return a list of uniforms implementing the world parameters.
106     */
107    public List<Uniform> getWorldBindUniforms() {
108        return worldBindUniforms;
109    }
110
111    /**
112     * Called by the material to tell the technique a parameter was modified
113     */
114    void notifySetParam(String paramName, VarType type, Object value) {
115        String defineName = def.getShaderParamDefine(paramName);
116        if (defineName != null) {
117            needReload = defines.set(defineName, type, value);
118        }
119        if (shader != null) {
120            updateUniformParam(paramName, type, value);
121        }
122    }
123
124    /**
125     * Called by the material to tell the technique a parameter was cleared
126     */
127    void notifyClearParam(String paramName) {
128        String defineName = def.getShaderParamDefine(paramName);
129        if (defineName != null) {
130            needReload = defines.remove(defineName);
131        }
132        if (shader != null) {
133            if (!paramName.startsWith("m_")) {
134                paramName = "m_" + paramName;
135            }
136            shader.removeUniform(paramName);
137        }
138    }
139
140    void updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner) {
141        Uniform u = shader.getUniform(paramName);
142
143//        if (ifNotOwner && u.getLastChanger() == owner)
144//            return;
145
146        switch (type) {
147            case Texture2D: // fall intentional
148            case Texture3D:
149            case TextureArray:
150            case TextureCubeMap:
151            case Int:
152                u.setValue(VarType.Int, value);
153                break;
154            default:
155                u.setValue(type, value);
156                break;
157        }
158//        u.setLastChanger(owner);
159    }
160
161    void updateUniformParam(String paramName, VarType type, Object value) {
162        updateUniformParam(paramName, type, value, false);
163    }
164
165    /**
166     * Returns true if the technique must be reloaded.
167     * <p>
168     * If a technique needs to reload, then the {@link Material} should
169     * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
170     * technique.
171     *
172     * @return true if the technique must be reloaded.
173     */
174    public boolean isNeedReload() {
175        return needReload;
176    }
177
178    /**
179     * Prepares the technique for use by loading the shader and setting
180     * the proper defines based on material parameters.
181     *
182     * @param assetManager The asset manager to use for loading shaders.
183     */
184    public void makeCurrent(AssetManager assetManager) {
185        // check if reload is needed..
186        if (def.isUsingShaders()) {
187            DefineList newDefines = new DefineList();
188            Collection<MatParam> params = owner.getParams();
189            for (MatParam param : params) {
190                String defineName = def.getShaderParamDefine(param.getName());
191                if (defineName != null) {
192                    newDefines.set(defineName, param.getVarType(), param.getValue());
193                }
194            }
195
196            if (!needReload && defines.getCompiled().equals(newDefines.getCompiled())) {
197                newDefines = null;
198                // defines have not been changed..
199            } else {
200                defines.clear();
201                defines.addFrom(newDefines);
202                // defines changed, recompile needed
203                loadShader(assetManager);
204            }
205        }
206    }
207
208    private void loadShader(AssetManager manager) {
209        // recompute define list
210        DefineList allDefines = new DefineList();
211        allDefines.addFrom(def.getShaderPresetDefines());
212        allDefines.addFrom(defines);
213
214        ShaderKey key = new ShaderKey(def.getVertexShaderName(),
215                def.getFragmentShaderName(),
216                allDefines,
217                def.getShaderLanguage());
218        shader = manager.loadShader(key);
219        if (shader == null) {
220            logger.warning("Failed to reload shader!");
221            return;
222        }
223
224        // refresh the uniform links
225        //owner.updateUniformLinks();
226
227        // register the world bound uniforms
228        worldBindUniforms.clear();
229        if (def.getWorldBindings() != null) {
230           for (UniformBinding binding : def.getWorldBindings()) {
231               Uniform uniform = shader.getUniform("g_" + binding.name());
232               uniform.setBinding(binding);
233               if (uniform != null) {
234                   worldBindUniforms.add(uniform);
235               }
236           }
237        }
238
239        needReload = false;
240    }
241
242    public void write(JmeExporter ex) throws IOException {
243        OutputCapsule oc = ex.getCapsule(this);
244        oc.write(def, "def", null);
245        // TODO:
246        // oc.write(owner, "owner", null);
247        oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
248        oc.write(defines, "defines", null);
249        oc.write(shader, "shader", null);
250    }
251
252    public void read(JmeImporter im) throws IOException {
253        InputCapsule ic = im.getCapsule(this);
254        def = (TechniqueDef) ic.readSavable("def", null);
255        worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
256        defines = (DefineList) ic.readSavable("defines", null);
257        shader = (Shader) ic.readSavable("shader", null);
258        //if (shader != null)
259        //    owner.updateUniformLinks();
260    }
261}
262