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 package com.jme3.material;
33 
34 import com.jme3.asset.AssetManager;
35 import com.jme3.export.*;
36 import com.jme3.shader.*;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.logging.Logger;
42 
43 /**
44  * Represents a technique instance.
45  */
46 public 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      */
Technique(Material owner, TechniqueDef def)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      */
Technique()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      */
getDef()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      */
getShader()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      */
getWorldBindUniforms()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      */
notifySetParam(String paramName, VarType type, Object value)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      */
notifyClearParam(String paramName)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 
updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner)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 
updateUniformParam(String paramName, VarType type, Object value)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      */
isNeedReload()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      */
makeCurrent(AssetManager assetManager)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 
loadShader(AssetManager manager)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 
write(JmeExporter ex)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 
read(JmeImporter im)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