/** * 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.jsr231; import java.nio.ByteBuffer; import java.nio.IntBuffer; import javax.media.opengl.GL; import org.jagatoo.logging.ProfileTimer; 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; import com.sun.opengl.util.BufferUtil; /** * 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 final IntBuffer tmpIntBuffer = BufferUtil.newIntBuffer( 1 ); private static ByteBuffer tmpByteBuffer = BufferUtil.newByteBuffer( 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 ) { final GL gl = ( (CanvasPeerImplBase)getContext() ).getGL(); gl.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( GL gl ) { String extensions = gl.glGetString( GL.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( GL gl ) { if ( !checkedOnce ) checkOnce( gl ); return ( ARB_shader_objects_supported ); } private static void compileShader( GL gl, 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" ); // get shader type final int shaderType; if ( s.getType() == ShaderType.VERTEX ) shaderType = GL.GL_VERTEX_SHADER_ARB; else shaderType = GL.GL_FRAGMENT_SHADER_ARB; // create shader object int glHandle = gl.glCreateShaderObjectARB( shaderType ); // set glhandle shaderOpenGLRef.setName( glHandle ); // create a new intbuffer for the source length tmpIntBuffer.clear(); tmpIntBuffer.put( s.getShaderCode().length() ); tmpIntBuffer.rewind(); // set source for shader gl.glShaderSourceARB( glHandle, 1, new String[] { s.getShaderCode() }, tmpIntBuffer ); // compile this shader gl.glCompileShaderARB( glHandle ); // check if everything went right: int[] params = new int[ 1 ]; gl.glGetObjectParameterivARB( glHandle, GL.GL_OBJECT_COMPILE_STATUS_ARB, params, 0 ); if ( params[ 0 ] != GL.GL_TRUE ) { if ( GLSLContext.isDebuggingEnabled() ) { tmpIntBuffer.clear(); gl.glGetObjectParameterivARB( glHandle, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, tmpIntBuffer ); final int length = tmpIntBuffer.get(); if ( tmpByteBuffer.limit() < length ) { tmpByteBuffer = BufferUtil.newByteBuffer( (int)( length * 1.5 ) ); } tmpIntBuffer.flip(); tmpByteBuffer.clear(); gl.glGetInfoLogARB( glHandle, length, 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: " + glHandle ); // set the compiled flag in the shader //s.setCompiled( true ); _SG_PrivilegedAccess.setDirty( s, false ); } private static void mapAttributes( GL gl, 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 ); gl.glBindAttribLocation( shaderProgOpenGLRef.getName(), index, name ); } } } private static void linkShaderProgram( GL gl, 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 = gl.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 gl.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 gl.glAttachObjectARB( glHandle, s.getOpenGLReferences().getReference( canvasPeer, shaderNameProvider ).getName() ); } // map the Vertex-Shader's Vertex-Attribute names to their indices. mapAttributes( gl, shaderProgram, shaderProgOpenGLRef ); // now we link the program gl.glLinkProgramARB( glHandle ); // check if we linked correctly int[] params = new int[ 1 ]; gl.glGetObjectParameterivARB( glHandle, GL.GL_OBJECT_LINK_STATUS_ARB, params, 0 ); if ( params[ 0 ] != GL.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 applyUniformVariables( GL gl, 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 int location = gl.glGetUniformLocationARB( shaderProgOpenGLRef.getName(), key ); // get value final float[] value = params.getUniformVarValueFloat( k ); switch ( params.getUniformVarBaseSizeFloat( k ) ) { case 1: gl.glUniform1fvARB( location, value.length, value, 0 ); break; case 2: gl.glUniform2fvARB( location, value.length / 2, value, 0 ); break; case 3: gl.glUniform3fvARB( location, value.length / 3, value, 0 ); break; case 4: gl.glUniform4fvARB( location, value.length / 4, value, 0 ); break; case 9: gl.glUniformMatrix3fv( location, value.length / 9, true, value, 0 ); break; case 16: gl.glUniformMatrix4fv( location, value.length / 16, true, value, 0 ); 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 int location = gl.glGetUniformLocationARB( shaderProgOpenGLRef.getName(), key ); // get value final int[] value = params.getUniformVarValueInt( k ); // send uniform var switch ( params.getUniformVarBaseSizeInt( k ) ) { case 1: gl.glUniform1ivARB( location, value.length, value, 0 ); break; case 2: gl.glUniform2ivARB( location, value.length / 2, value, 0 ); break; case 3: gl.glUniform3ivARB( location, value.length / 3, value, 0 ); break; case 4: gl.glUniform4ivARB( location, value.length / 4, value, 0 ); break; } } } protected static final int getCurrentShaderProgram( GL gl ) { if ( !checkedOnce ) checkOnce( gl ); if ( !ARB_shader_objects_supported ) { return ( 0 ); } return ( gl.glGetHandleARB( GL.GL_PROGRAM_OBJECT_ARB ) ); } protected static final void disableGLSLShaders( GL gl, OpenGLStatesCache statesCache ) { if ( statesCache.currentGLSLShaderProgram == 0 ) return; if ( !checkedOnce ) checkOnce( gl ); if ( ARB_shader_objects_supported ) { gl.glUseProgramObjectARB( 0 ); } statesCache.currentGLSLShaderProgram = 0; } public static final void apply( Object glObj, GLSLContext shaderProgram, CanvasPeer canvasPeer, OpenGLStatesCache statesCache, RenderMode renderMode ) { // start profiling ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "GLSLShaderProgramShaderPeer:shade" ); // get a reference on our gl implmeentation final GL gl = (GL)glObj; if ( renderMode != RenderMode.NORMAL ) { disableGLSLShaders( gl, statesCache ); return; } // check if shaders are supported if ( !checkedOnce ) checkOnce( gl ); 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( gl, 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( gl, s, shaderOpenGLRef ); } // now we can link the shader program linkShaderProgram( gl, 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 ) { gl.glUseProgramObjectARB( shaderProgGLHandle ); statesCache.currentGLSLShaderProgram = shaderProgGLHandle; } // apply uniform variables applyUniformVariables( gl, shaderProgram, canvasPeer ); } } else { disableGLSLShaders( gl, statesCache ); } // end profile ProfileTimer.endProfile(); } }