blob: 265eed6a0a8e276aaa3c69fd7902d12f162a013b [file] [log] [blame]
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.shader;
import com.jme3.export.*;
import com.jme3.renderer.Renderer;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.ListMap;
import com.jme3.util.NativeObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
public final class Shader extends NativeObject implements Savable {
private String language;
/**
* True if the shader is fully compiled & linked.
* (e.g no GL error will be invoked if used).
*/
private boolean usable = false;
/**
* A list of all shaders currently attached.
*/
private ArrayList<ShaderSource> shaderList;
/**
* Maps uniform name to the uniform variable.
*/
// private HashMap<String, Uniform> uniforms;
private ListMap<String, Uniform> uniforms;
/**
* Maps attribute name to the location of the attribute in the shader.
*/
private IntMap<Attribute> attribs;
/**
* Type of shader. The shader will control the pipeline of it's type.
*/
public static enum ShaderType {
/**
* Control fragment rasterization. (e.g color of pixel).
*/
Fragment,
/**
* Control vertex processing. (e.g transform of model to clip space)
*/
Vertex,
/**
* Control geometry assembly. (e.g compile a triangle list from input data)
*/
Geometry;
}
/**
* Shader source describes a shader object in OpenGL. Each shader source
* is assigned a certain pipeline which it controls (described by it's type).
*/
public static class ShaderSource extends NativeObject implements Savable {
ShaderType shaderType;
boolean usable = false;
String name = null;
String source = null;
String defines = null;
public ShaderSource(ShaderType type){
super(ShaderSource.class);
this.shaderType = type;
if (type == null)
throw new NullPointerException("The shader type must be specified");
}
protected ShaderSource(ShaderSource ss){
super(ShaderSource.class, ss.id);
this.shaderType = ss.shaderType;
usable = false;
name = ss.name;
// forget source & defines
}
public ShaderSource(){
super(ShaderSource.class);
}
public void write(JmeExporter ex) throws IOException{
OutputCapsule oc = ex.getCapsule(this);
oc.write(shaderType, "shaderType", null);
oc.write(name, "name", null);
oc.write(source, "source", null);
oc.write(defines, "defines", null);
}
public void read(JmeImporter im) throws IOException{
InputCapsule ic = im.getCapsule(this);
shaderType = ic.readEnum("shaderType", ShaderType.class, null);
name = ic.readString("name", null);
source = ic.readString("source", null);
defines = ic.readString("defines", null);
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public ShaderType getType() {
return shaderType;
}
public void setSource(String source){
if (source == null)
throw new NullPointerException("Shader source cannot be null");
this.source = source;
setUpdateNeeded();
}
public void setDefines(String defines){
if (defines == null)
throw new NullPointerException("Shader defines cannot be null");
this.defines = defines;
setUpdateNeeded();
}
public String getSource(){
return source;
}
public String getDefines(){
return defines;
}
public boolean isUsable(){
return usable;
}
public void setUsable(boolean usable){
this.usable = usable;
}
@Override
public String toString(){
String nameTxt = "";
if (name != null)
nameTxt = "name="+name+", ";
if (defines != null)
nameTxt += "defines, ";
return getClass().getSimpleName() + "["+nameTxt+"type="
+ shaderType.name()+"]";
}
public void resetObject(){
id = -1;
usable = false;
setUpdateNeeded();
}
public void deleteObject(Object rendererObject){
((Renderer)rendererObject).deleteShaderSource(ShaderSource.this);
}
public NativeObject createDestructableClone(){
return new ShaderSource(ShaderSource.this);
}
}
/**
* Create an empty shader.
*/
public Shader(String language){
super(Shader.class);
this.language = language;
shaderList = new ArrayList<ShaderSource>();
// uniforms = new HashMap<String, Uniform>();
uniforms = new ListMap<String, Uniform>();
attribs = new IntMap<Attribute>();
}
/**
* Do not use this constructor. Serialization purposes only.
*/
public Shader(){
super(Shader.class);
}
protected Shader(Shader s){
super(Shader.class, s.id);
shaderList = new ArrayList<ShaderSource>();
//uniforms = new ListMap<String, Uniform>();
//attribs = new IntMap<Attribute>();
// NOTE: Because ShaderSources are registered separately with
// the GLObjectManager
for (ShaderSource source : s.shaderList){
shaderList.add( (ShaderSource)source.createDestructableClone() );
}
}
public void write(JmeExporter ex) throws IOException{
OutputCapsule oc = ex.getCapsule(this);
oc.write(language, "language", null);
oc.writeSavableArrayList(shaderList, "shaderList", null);
oc.writeIntSavableMap(attribs, "attribs", null);
oc.writeStringSavableMap(uniforms, "uniforms", null);
}
public void read(JmeImporter im) throws IOException{
InputCapsule ic = im.getCapsule(this);
language = ic.readString("language", null);
shaderList = ic.readSavableArrayList("shaderList", null);
attribs = (IntMap<Attribute>) ic.readIntSavableMap("attribs", null);
HashMap<String, Uniform> uniMap = (HashMap<String, Uniform>) ic.readStringSavableMap("uniforms", null);
uniforms = new ListMap<String, Uniform>(uniMap);
}
/**
* Creates a deep clone of the shader, where the sources are available
* but have not been compiled yet. Does not copy the uniforms or attribs.
* @return
*/
// public Shader createDeepClone(String defines){
// Shader newShader = new Shader(language);
// for (ShaderSource source : shaderList){
// if (!source.getDefines().equals(defines)){
// // need to clone the shadersource so
// // the correct defines can be placed
// ShaderSource newSource = new ShaderSource(source.getType());
// newSource.setSource(source.getSource());
// newSource.setDefines(defines);
// newShader.addSource(newSource);
// }else{
// // no need to clone source, also saves
// // having to compile the shadersource
// newShader.addSource(source);
// }
// }
// return newShader;
// }
/**
* Adds source code to a certain pipeline.
*
* @param type The pipeline to control
* @param source The shader source code (in GLSL).
*/
public void addSource(ShaderType type, String name, String source, String defines){
ShaderSource shader = new ShaderSource(type);
shader.setSource(source);
shader.setName(name);
if (defines != null)
shader.setDefines(defines);
shaderList.add(shader);
setUpdateNeeded();
}
public void addSource(ShaderType type, String source, String defines){
addSource(type, null, source, defines);
}
public void addSource(ShaderType type, String source){
addSource(type, source, null);
}
/**
* Adds an existing shader source to this shader.
* @param source
*/
private void addSource(ShaderSource source){
shaderList.add(source);
setUpdateNeeded();
}
public Uniform getUniform(String name){
Uniform uniform = uniforms.get(name);
if (uniform == null){
uniform = new Uniform();
uniform.name = name;
uniforms.put(name, uniform);
}
return uniform;
}
public void removeUniform(String name){
uniforms.remove(name);
}
public Attribute getAttribute(VertexBuffer.Type attribType){
int ordinal = attribType.ordinal();
Attribute attrib = attribs.get(ordinal);
if (attrib == null){
attrib = new Attribute();
attrib.name = attribType.name();
attribs.put(ordinal, attrib);
}
return attrib;
}
// public Collection<Uniform> getUniforms(){
// return uniforms.values();
// }
public ListMap<String, Uniform> getUniformMap(){
return uniforms;
}
// public Collection<Attribute> getAttributes() {
// return attribs.
// }
public Collection<ShaderSource> getSources(){
return shaderList;
}
public String getLanguage(){
return language;
}
@Override
public String toString(){
return getClass().getSimpleName() + "[language="+language
+ ", numSources="+shaderList.size()
+ ", numUniforms="+uniforms.size()
+ ", shaderSources="+getSources()+"]";
}
/**
* Clears all sources. Assuming that they have already been detached and
* removed on the GL side.
*/
public void resetSources(){
shaderList.clear();
}
/**
* Returns true if this program and all it's shaders have been compiled,
* linked and validated successfuly.
*/
public boolean isUsable(){
return usable;
}
/**
* Sets if the program can be used. Should only be called by the Renderer.
* @param usable
*/
public void setUsable(boolean usable){
this.usable = usable;
}
/**
* Usually called when the shader itself changes or during any
* time when the var locations need to be refreshed.
*/
public void resetLocations(){
// NOTE: Shader sources will be reset seperately from the shader itself.
for (Uniform uniform : uniforms.values()){
uniform.reset(); // fixes issue with re-initialization
}
for (Entry<Attribute> entry : attribs){
entry.getValue().location = -2;
}
}
@Override
public void setUpdateNeeded(){
super.setUpdateNeeded();
resetLocations();
}
/**
* Called by the object manager to reset all object IDs. This causes
* the shader to be reuploaded to the GPU incase the display was restarted.
*/
@Override
public void resetObject() {
this.id = -1;
this.usable = false;
for (ShaderSource source : shaderList){
source.resetObject();
}
setUpdateNeeded();
}
@Override
public void deleteObject(Object rendererObject) {
((Renderer)rendererObject).deleteShader(this);
}
public NativeObject createDestructableClone(){
return new Shader(this);
}
}