/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* 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 the 'Xith3D Project Group' 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) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.render.lwjgl;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.jagatoo.logging.ProfileTimer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.xith3d.render.CanvasPeer;
import org.xith3d.render.OpenGLStatesCache;
import org.xith3d.render.OpenGlExtensions;
import org.xith3d.render.SceneGraphOpenGLReference;
import org.xith3d.render.SceneGraphOpenGLReferences;
import org.xith3d.render.RenderPeer.RenderMode;
import org.xith3d.scenegraph.GLSLContext;
import org.xith3d.scenegraph.GLSLFragmentShader;
import org.xith3d.scenegraph.GLSLParameters;
import org.xith3d.scenegraph.GLSLShader;
import org.xith3d.scenegraph.GLSLShaderProgram;
import org.xith3d.scenegraph.GLSLVertexShader;
import org.xith3d.scenegraph._SG_PrivilegedAccess;
import org.xith3d.scenegraph.Shader.ShaderType;
import org.xith3d.utility.debug.DebugStrings;
import org.xith3d.utility.logging.X3DLog;
/**
* Created on Jul 7, 2006 by florian for project 'xith3d_glsl_shader_support'
*
* @author Florian Hofmann (aka Goliat)
* @author Marvin Froehlich (aka Qudus)
*/
public class GLSLShaderProgramStateUnitPeer
{
private static boolean ARB_shader_objects_supported = false;
private static ByteBuffer tmpByteBuffer = BufferUtils.createByteBuffer( 128 );
private static FloatBuffer tmpFloatBuffer = BufferUtils.createFloatBuffer( 128 );
private static IntBuffer tmpIntBuffer = BufferUtils.createIntBuffer( 128 );
private static SceneGraphOpenGLReferences.Provider shaderProgramNameProvider = new SceneGraphOpenGLReferences.Provider()
{
public SceneGraphOpenGLReference newReference( CanvasPeer canvasPeer, SceneGraphOpenGLReferences references, int numNamesPerContext )
{
return ( new SceneGraphOpenGLReference( canvasPeer, references, numNamesPerContext )
{
@Override
public void prepareObjectForDestroy()
{
SceneGraphOpenGLReference ref = getReferences().removeReference( getContext().getCanvasID() );
( (CanvasPeerImplBase)getContext() ).addDestroyableObject( ref );
}
@Override
public void destroyObject( int index, int name )
{
ARBShaderObjects.glDeleteObjectARB( name );
}
} );
}
};
private static SceneGraphOpenGLReferences.Provider shaderNameProvider = new SceneGraphOpenGLReferences.Provider()
{
public SceneGraphOpenGLReference newReference( CanvasPeer canvasPeer, SceneGraphOpenGLReferences references, int numNamesPerContext )
{
return ( new SceneGraphOpenGLReference( canvasPeer, references, numNamesPerContext )
{
@Override
public void prepareObjectForDestroy()
{
SceneGraphOpenGLReference ref = getReferences().removeReference( getContext().getCanvasID() );
( (CanvasPeerImplBase)getContext() ).addDestroyableObject( ref );
}
@Override
public void destroyObject( int index, int name )
{
//ARBShaderObjects.glDeleteObjectARB( name );
}
} );
}
};
private static boolean checkedOnce = false;
/**
* check if GL_ARB_fragment_shader and GL_ARB_vertex_shader are supported
*
* @param gl
*/
private static void checkOnce()
{
String extensions = GL11.glGetString( GL11.GL_EXTENSIONS );
if ( extensions.indexOf( "GL_ARB_shader_objects" ) != -1 )
{
ARB_shader_objects_supported = true;
}
else
{
X3DLog.error( "GL_ARB_fragment_shader and/or GL_ARB_vertex_shader not supported, skipping" );
}
checkedOnce = true;
}
protected static final boolean areARBShaderObjectsSupported()
{
if ( !checkedOnce )
checkOnce();
return ( ARB_shader_objects_supported );
}
private static void compileShader( GLSLShader s, SceneGraphOpenGLReference shaderOpenGLRef )
{
// check if we tried to compile this before and failed
if ( s.hasCompilationError() )
return;
X3DLog.debug( "compiling Shader:\n", s.getShaderCode(), "\n" );
// create shader object
final int shaderHandle;
if ( s.getType() == ShaderType.VERTEX )
shaderHandle = ARBShaderObjects.glCreateShaderObjectARB( ARBVertexShader.GL_VERTEX_SHADER_ARB );
else
shaderHandle = ARBShaderObjects.glCreateShaderObjectARB( ARBFragmentShader.GL_FRAGMENT_SHADER_ARB );
// set glHandle
shaderOpenGLRef.setName( shaderHandle );
// create byte buffer for source
if ( tmpByteBuffer.limit() < s.getShaderCode().length() )
{
tmpByteBuffer = BufferUtils.createByteBuffer( (int)( s.getShaderCode().length() * 1.5 ) );
}
tmpByteBuffer.clear();
tmpByteBuffer.put( s.getShaderCode().getBytes() );
tmpByteBuffer.flip();
// set source for shader
ARBShaderObjects.glShaderSourceARB( shaderHandle, tmpByteBuffer );
// compile this shader
ARBShaderObjects.glCompileShaderARB( shaderHandle );
// check if everything went right:
tmpIntBuffer.clear();
ARBShaderObjects.glGetObjectParameterARB( shaderHandle, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB, tmpIntBuffer );
if ( tmpIntBuffer.get() != GL11.GL_TRUE )
{
if ( GLSLContext.isDebuggingEnabled() )
{
tmpIntBuffer.clear();
ARBShaderObjects.glGetObjectParameterARB( shaderHandle, ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB, tmpIntBuffer );
final int length = tmpIntBuffer.get();
if ( tmpByteBuffer.limit() < length )
{
tmpByteBuffer = BufferUtils.createByteBuffer( (int)( length * 1.5 ) );
}
tmpIntBuffer.flip();
tmpByteBuffer.clear();
ARBShaderObjects.glGetInfoLogARB( shaderHandle, tmpIntBuffer, tmpByteBuffer );
byte[] infoBytes = new byte[ length ];
tmpByteBuffer.get( infoBytes );
String errMsg = new String( infoBytes );
String message = "Failed to compile GLSL Shader.\n" + "Message:\n" + errMsg + "\n" + "+++++++++++++++++++++++++++++++++++\n" + "Source:\n" + "+++++++++++++++++++++++++++++++++++\n" + DebugStrings.numerateLines( s.getShaderCode(), 1, 3 ) + "\n" + "+++++++++++++++++++++++++++++++++++\n";
System.err.println( message );
X3DLog.error( message );
}
s.setCompilationError( true );
return;
}
X3DLog.debug( "done glHandle: ", shaderHandle );
// set the compiled flag in the shader
//s.setCompiled( true );
_SG_PrivilegedAccess.setDirty( s, false );
}
private static void mapAttributes( GLSLShaderProgram shaderProgram, SceneGraphOpenGLReference shaderProgOpenGLRef )
{
if ( !OpenGlExtensions.GL_CUSTOM_VERTEX_ATTRIBUTES )
return;
for ( int i = 0; i < shaderProgram.getNumVertexShaders(); i++ )
{
GLSLVertexShader shader = (GLSLVertexShader)shaderProgram.getVertexShader( i );
final int numAttribs = shader.getVertexAttributesCount();
for ( int j = 0; j < numAttribs; j++ )
{
final int index = shader.getNthVertexAttributeIndex( j );
final String name = shader.getVertexAttributeMapping( index );
setupTempByteBuffer( name.length() + 1 );
tmpByteBuffer.put( name.getBytes() );
tmpByteBuffer.put( (byte)0 );
tmpByteBuffer.flip();
GL20.glBindAttribLocation( shaderProgOpenGLRef.getName(), index, tmpByteBuffer );
}
}
}
private static void linkShaderProgram( GLSLShaderProgram shaderProgram, CanvasPeer canvasPeer )
{
// check if we have tried to link this before
if ( shaderProgram.hasLinkingError() )
return;
X3DLog.debug( "linking GLSL shader program: ", shaderProgram.getName() );
final SceneGraphOpenGLReference shaderProgOpenGLRef = shaderProgram.getOpenGLReferences().getReference( canvasPeer, shaderProgramNameProvider );
// create a program container
final int glHandle = ARBShaderObjects.glCreateProgramObjectARB();
// set the handle in the program
shaderProgOpenGLRef.setName( glHandle );
// attach all shaders (which were compiled before)
for ( int i = 0; i < shaderProgram.getNumVertexShaders(); i++ )
{
GLSLVertexShader s = (GLSLVertexShader)shaderProgram.getVertexShader( i );
// attach shader to program
ARBShaderObjects.glAttachObjectARB( glHandle, s.getOpenGLReferences().getReference( canvasPeer, shaderNameProvider ).getName() );
}
for ( int i = 0; i < shaderProgram.getNumFragmentShaders(); i++ )
{
GLSLFragmentShader s = (GLSLFragmentShader)shaderProgram.getFragmentShader( i );
// attach shader to program
ARBShaderObjects.glAttachObjectARB( glHandle, s.getOpenGLReferences().getReference( canvasPeer, shaderNameProvider ).getName() );
}
// map the Vertex-Shader's Vertex-Attribute names to their indices.
mapAttributes( shaderProgram, shaderProgOpenGLRef );
// now we link the program
ARBShaderObjects.glLinkProgramARB( glHandle );
// check if we linked correctly
//IntBuffer params = BufferUtils.createIntBuffer( 1 );
tmpIntBuffer.clear();
ARBShaderObjects.glGetObjectParameterARB( glHandle, ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB, tmpIntBuffer );
if ( tmpIntBuffer.get() != GL11.GL_TRUE )
{
X3DLog.error( "failed to link glsl program shader id:", glHandle );
_SG_PrivilegedAccess.setGLSLShaderProgramLinkError( shaderProgram, true );
return;
}
// give out some debuggin stuff
X3DLog.debug( "done ... glHandle: ", glHandle );
// set this to linked
_SG_PrivilegedAccess.setGLSLShaderProgramLinked( shaderProgram, true );
}
private static void setupTempByteBuffer( int minCap )
{
if ( tmpByteBuffer.capacity() < minCap )
{
tmpByteBuffer = BufferUtils.createByteBuffer( (int)( minCap * 1.5 ) );
}
tmpByteBuffer.position( 0 );
tmpByteBuffer.limit( tmpByteBuffer.capacity() );
}
private static void setupTempFloatBuffer( int minCap )
{
if ( tmpFloatBuffer.capacity() < minCap )
{
tmpFloatBuffer = BufferUtils.createFloatBuffer( (int)( minCap * 1.5 ) );
}
tmpFloatBuffer.position( 0 );
tmpFloatBuffer.limit( tmpFloatBuffer.capacity() );
}
private static void setupTempIntBuffer( int minCap )
{
if ( tmpIntBuffer.capacity() < minCap )
{
tmpIntBuffer = BufferUtils.createIntBuffer( (int)( minCap * 1.5 ) );
}
tmpIntBuffer.position( 0 );
tmpIntBuffer.limit( tmpIntBuffer.capacity() );
}
private static void applyUniformVariables( GLSLContext shaderProgram, CanvasPeer canvasPeer )
{
final GLSLShaderProgram program = shaderProgram.getProgram();
final GLSLParameters params = shaderProgram.getUniformParameters();
// check if we have tried to link this before
if ( program.hasLinkingError() )
return;
if ( !params.hasUniformVars() )
return;
final SceneGraphOpenGLReference shaderProgOpenGLRef = program.getOpenGLReferences().getReference( canvasPeer, shaderProgramNameProvider );
final int numFloatUniformVars = params.getNumUniformVarsFloat();
// loop through the keys
for ( int k = 0; k < numFloatUniformVars; k++ )
{
final String key = params.getFloatUniformVarName( k );
// create a location for this key
setupTempByteBuffer( key.length() + 1 );
tmpByteBuffer.put( key.getBytes() );
tmpByteBuffer.put( (byte)0 );
tmpByteBuffer.flip();
final int location = ARBShaderObjects.glGetUniformLocationARB( shaderProgOpenGLRef.getName(), tmpByteBuffer );
// get value
final float[] value = params.getUniformVarValueFloat( k );
// send uniform var
setupTempFloatBuffer( value.length );
tmpFloatBuffer.put( value );
tmpFloatBuffer.flip();
switch ( params.getUniformVarBaseSizeFloat( k ) )
{
case 1:
ARBShaderObjects.glUniform1ARB( location, tmpFloatBuffer );
break;
case 2:
ARBShaderObjects.glUniform2ARB( location, tmpFloatBuffer );
break;
case 3:
ARBShaderObjects.glUniform3ARB( location, tmpFloatBuffer );
break;
case 4:
ARBShaderObjects.glUniform4ARB( location, tmpFloatBuffer );
break;
case 9:
ARBShaderObjects.glUniformMatrix3ARB( location, true, tmpFloatBuffer );
break;
case 16:
ARBShaderObjects.glUniformMatrix4ARB( location, true, tmpFloatBuffer );
break;
}
}
final int numIntUniformVars = params.getNumUniformVarsInt();
// loop through the keys
for ( int k = 0; k < numIntUniformVars; k++ )
{
final String key = params.getIntUniformVarName( k );
// create a location for this key
setupTempByteBuffer( key.length() + 1 );
tmpByteBuffer.put( key.getBytes() );
tmpByteBuffer.put( (byte)0 );
tmpByteBuffer.flip();
final int location = ARBShaderObjects.glGetUniformLocationARB( shaderProgOpenGLRef.getName(), tmpByteBuffer );
// get value
final int[] value = params.getUniformVarValueInt( k );
// send uniform var
setupTempIntBuffer( value.length );
tmpIntBuffer.put( value );
tmpIntBuffer.flip();
switch ( params.getUniformVarBaseSizeInt( k ) )
{
case 1:
ARBShaderObjects.glUniform1ARB( location, tmpIntBuffer );
break;
case 2:
ARBShaderObjects.glUniform2ARB( location, tmpIntBuffer );
break;
case 3:
ARBShaderObjects.glUniform3ARB( location, tmpIntBuffer );
break;
case 4:
ARBShaderObjects.glUniform4ARB( location, tmpIntBuffer );
break;
}
}
}
protected static final int getCurrentShaderProgram()
{
if ( !checkedOnce )
checkOnce();
if ( !ARB_shader_objects_supported )
{
return ( 0 );
}
return ( ARBShaderObjects.glGetHandleARB( ARBShaderObjects.GL_PROGRAM_OBJECT_ARB ) );
}
protected static final void disableGLSLShaders( OpenGLStatesCache statesCache )
{
if ( statesCache.enabled && statesCache.currentGLSLShaderProgram == 0 )
return;
if ( !checkedOnce )
checkOnce();
if ( ARB_shader_objects_supported )
{
ARBShaderObjects.glUseProgramObjectARB( 0 );
}
statesCache.currentGLSLShaderProgram = 0;
}
public static final void apply( GLSLContext shaderProgram, CanvasPeer canvasPeer, OpenGLStatesCache statesCache, RenderMode renderMode )
{
// start profiling
ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "GLSLShaderProgramStateUnitPeer::apply()" );
if ( renderMode != RenderMode.NORMAL )
{
disableGLSLShaders( statesCache );
return;
}
// check if shaders are supported
if ( !checkedOnce )
checkOnce();
final GLSLShaderProgram program = shaderProgram.getProgram();
// check if this program was linked
if ( shaderProgram.isEnabled() && !program.isLinked() )
{
// we have to link it ...
// but first we have to check that all shaders got compiled
for ( int i = 0; i < program.getNumVertexShaders(); i++ )
{
final GLSLShader s = program.getVertexShader( i );
final SceneGraphOpenGLReference shaderOpenGLRef = s.getOpenGLReferences().getReference( canvasPeer, shaderNameProvider );
// compile if necessary
//if ( !s.isCompiled() )
if ( _SG_PrivilegedAccess.isDirty( s ) )
compileShader( s, shaderOpenGLRef );
}
for ( int i = 0; i < program.getNumFragmentShaders(); i++ )
{
final GLSLShader s = program.getFragmentShader( i );
final SceneGraphOpenGLReference shaderOpenGLRef = s.getOpenGLReferences().getReference( canvasPeer, shaderNameProvider );
// compile if necessary
//if ( !s.isCompiled() )
if ( _SG_PrivilegedAccess.isDirty( s ) )
compileShader( s, shaderOpenGLRef );
}
// now we can link the shader program
linkShaderProgram( program, canvasPeer );
}
if ( shaderProgram.isEnabled() )
{
// check if this program is linked
// in cases of failed linking this can happen
if ( program.isLinked() && ARB_shader_objects_supported )
{
final int shaderProgGLHandle = program.getOpenGLReferences().getReference( canvasPeer, shaderProgramNameProvider ).getName();
X3DLog.debug( "Use GLSL program id ", shaderProgGLHandle );
if ( !statesCache.enabled || statesCache.currentGLSLShaderProgram != shaderProgGLHandle )
{
ARBShaderObjects.glUseProgramObjectARB( shaderProgGLHandle );
statesCache.currentGLSLShaderProgram = shaderProgGLHandle;
}
// apply uniform variables
applyUniformVariables( shaderProgram, canvasPeer );
}
}
else
{
disableGLSLShaders( statesCache );
}
// end profile
ProfileTimer.endProfile();
}
}