|
From: <cn...@us...> - 2009-03-20 03:38:01
|
Revision: 187
http://hgengine.svn.sourceforge.net/hgengine/?rev=187&view=rev
Author: cnlohr
Date: 2009-03-20 03:37:43 +0000 (Fri, 20 Mar 2009)
Log Message:
-----------
add shaders (not yet working)
Added Paths:
-----------
Mercury2/src/Shader.cpp
Mercury2/src/Shader.h
Added: Mercury2/src/Shader.cpp
===================================================================
--- Mercury2/src/Shader.cpp (rev 0)
+++ Mercury2/src/Shader.cpp 2009-03-20 03:37:43 UTC (rev 187)
@@ -0,0 +1,431 @@
+#include <Shader.h>
+#include <RenderableNode.h>
+#include <MercuryFile.h>
+
+#define GL_GLEXT_PROTOTYPES
+
+#include <GL/gl.h>
+#include <GL/glext.h>
+
+using namespace std;
+
+REGISTER_ASSET_TYPE( Shader );
+
+ShaderAttributesSet SHADERATTRIBUTES;
+Shader * Shader::CurrentShader;
+
+ShaderAttribute * ShaderAttributesSet::GetHandle( const MString & sName )
+{
+ ShaderAttribute * ret = m_AllShaderAttributes[sName];
+ if( !ret )
+ {
+ ret = new ShaderAttribute();
+ m_AllShaderAttributes[sName] = ret;
+ }
+ return ret;
+}
+
+Shader::Shader()
+{
+ iProgramID = (GLhandleARB)NULL;
+ vertexShader = (GLhandleARB)NULL;
+ fragmentShader = (GLhandleARB)NULL;
+ geometryShader = (GLhandleARB)NULL;
+ iTimeCode[0] = 0;
+ iTimeCode[1] = 0;
+ iTimeCode[2] = 0;
+
+}
+
+Shader::~Shader()
+{
+ DestroyShader( );
+}
+
+void Shader::Init(MercuryNode* node)
+{
+ MercuryAsset::Init( node );
+ RenderableNode* rn;
+ if ( (rn=RenderableNode::Cast( node )) )
+ rn->AddPostRender( this );
+}
+
+void Shader::Render(const MercuryNode* node)
+{
+ bool bApply = true;
+
+ //If there's a currnet shader, we may want to abort switching shaders
+ if( CurrentShader )
+ {
+ if( CurrentShader->fPriority > fPriority )
+ bApply = false;
+ }
+ if( bApply )
+ {
+ OriginalShader = CurrentShader;
+ CurrentShader = this;
+ ActivateShader();
+ }
+}
+
+void Shader::PostRender(const MercuryNode* node)
+{
+ CurrentShader = OriginalShader;
+ if( OriginalShader )
+ OriginalShader->ActivateShader();
+ else
+ DeactivateShader();
+}
+
+void Shader::LoadFromXML(const XMLNode& node)
+{
+ sShaderName = node.Attribute("file");
+ fPriority = atof( node.Attribute("priority").c_str() );
+ LoadShader( );
+}
+
+bool Shader::LoadShader( )
+{
+ GetTimeCodes( iTimeCode );
+ MString s1 = sShaderName, s2 = sShaderName, s3 = sShaderName;
+ MercuryFile * f1;
+ MercuryFile * f2;
+ MercuryFile * f3; //geometry
+ char * Buffer;
+ int i;
+
+ s1 += ".frag";
+ s2 += ".vert";
+ s3 += ".geom";
+ f1 = FILEMAN.Open( s1 );
+ f2 = FILEMAN.Open( s2 );
+ f3 = FILEMAN.Open( s3 );
+
+ if( f1 == 0 || f2 == 0 )
+ {
+ if( !f1 )
+ printf( "Could not open %s.\n", (char*)s1.c_str() );
+ if( !f2 )
+ printf( "Could not open %s.\n", (char*)s2.c_str() );
+ return false;
+ }
+ if( f1 )
+ {
+ i = f1->Length();
+ Buffer = (char*)malloc( i+1 );
+ f1->Read( Buffer, i );
+ f1->Close();
+ printf( "Compiling: %s\n", s1.c_str() );
+ Buffer[i] = '\0';
+ if( !LoadShaderFrag( Buffer ) )
+ {
+ free( Buffer );
+ if( f2 )
+ f2->Close();
+ if( f3 )
+ f3->Close();
+ printf( "Reporting failed shaderload. Not linking.\n" );
+ return false;
+ }
+ free( Buffer );
+ }
+ if( f2 )
+ {
+ i = f2->Length();
+ Buffer = (char*)malloc( i+1 );
+ f2->Read( Buffer, i );
+ f2->Close();
+ Buffer[i] = '\0';
+ printf( "Compiling: %s\n", s2.c_str() );
+ if( !LoadShaderVert( Buffer ) )
+ {
+ if( f3 )
+ f3->Close();
+ free( Buffer );
+ printf( "Reporting failed shaderload. Not linking.\n" );
+ return false;
+ }
+ free( Buffer );
+ }
+ if( f3 )
+ {
+ i = f3->Length();
+ Buffer = (char*)malloc( i+1 );
+ f3->Read( Buffer, i );
+ f3->Close();
+ Buffer[i] = '\0';
+ printf( "Compiling: %s\n", s3.c_str() );
+ if( !LoadShaderGeom( Buffer ) )
+ {
+ free( Buffer );
+ printf( "Reporting failed shaderload. Not linking.\n" );
+ return false;
+ }
+ free( Buffer );
+ }
+ return LinkShaders();
+}
+
+bool Shader::LoadShaderFrag( const char * sShaderCode )
+{
+ if( strlen( sShaderCode ) < 5 )
+ return false;
+
+ GLint bFragCompiled;
+ GLint stringLength;
+ fragmentShader = glCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB );
+ glShaderSourceARB( fragmentShader, 1, &sShaderCode, NULL );
+ glCompileShaderARB( fragmentShader );
+
+ glGetObjectParameterivARB( fragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &bFragCompiled );
+ glGetObjectParameterivARB( fragmentShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &stringLength );
+ if ( stringLength > 1 )
+ {
+ char * tmpstr = (char*)malloc( stringLength + 1 );
+ glGetInfoLogARB( fragmentShader, stringLength, NULL, tmpstr );
+ puts( "Compiling Fragment Shader response follows:" );
+ puts( tmpstr );
+ free( tmpstr );
+ return bFragCompiled!=0;
+ }
+ return true;
+}
+
+bool Shader::LoadShaderVert( const char * sShaderCode )
+{
+ if( strlen( sShaderCode ) < 5 )
+ return false;
+
+ GLint bVertCompiled;
+ GLint stringLength;
+ //Create a new vertex shader
+ vertexShader = glCreateShaderObjectARB( GL_VERTEX_SHADER_ARB );
+ //Bind the shader to the text, setting that to be its source.
+ glShaderSourceARB( vertexShader, 1, &sShaderCode, NULL );
+ //Compile the shader
+ glCompileShaderARB( vertexShader );
+ //Did the shader compile? Were there any errors?
+ glGetObjectParameterivARB( vertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &bVertCompiled );
+ glGetObjectParameterivARB( vertexShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &stringLength );
+ if( stringLength > 1 )
+ {
+ char * tmpstr = (char*)malloc( stringLength + 1 );
+ glGetInfoLogARB( vertexShader, stringLength, NULL, tmpstr );
+ puts( "Compiling Vertex Shader response follows:" );
+ puts( tmpstr );
+ free( tmpstr );
+ return bVertCompiled!=0;
+ }
+
+ return true;
+}
+
+bool Shader::LoadShaderGeom( const char * sShaderCode )
+{
+ if( strlen( sShaderCode ) < 5 )
+ return false;
+
+ GLint bGeomCompiled;
+ GLint stringLength;
+ //Create a new geometry shader
+ geometryShader = glCreateShaderObjectARB( GL_GEOMETRY_SHADER_EXT );
+ //Bind the shader to the text, setting that to be its source.
+ glShaderSourceARB( geometryShader, 1, &sShaderCode, NULL );
+ //Compile the shader
+ glCompileShaderARB( geometryShader );
+ //Did the shader compile? Were there any errors?
+ glGetObjectParameterivARB( geometryShader, GL_OBJECT_COMPILE_STATUS_ARB, &bGeomCompiled );
+ glGetObjectParameterivARB( geometryShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &stringLength );
+ if( bGeomCompiled == 0 )
+ {
+ char * tmpstr = (char*)malloc( stringLength + 1 );
+ glGetInfoLogARB( geometryShader, stringLength, NULL, tmpstr );
+ puts( "Compiling Geometry Shader response follows:" );
+ puts( tmpstr );
+ free( tmpstr );
+ return bGeomCompiled!=0;
+ }
+ return true;
+}
+
+bool Shader::LinkShaders()
+{
+ GLint bLinked;
+ GLint stringLength;
+ //Create the actual shader prgoram
+ iProgramID = glCreateProgramObjectARB();
+ //Attach the fragment/vertex shader to it.
+ if( vertexShader )
+ glAttachObjectARB( iProgramID, vertexShader );
+ if( fragmentShader )
+ glAttachObjectARB( iProgramID, fragmentShader );
+ if( geometryShader )
+ glAttachObjectARB( iProgramID, geometryShader );
+ //Attempt to link the shader
+ glLinkProgramARB( iProgramID );
+
+ //If we're using a geometry shader, we have to do a little extra.
+ if( geometryShader )
+ {
+ glProgramParameteriEXT( iProgramID, GL_GEOMETRY_INPUT_TYPE_EXT, GL_TRIANGLES );
+ glProgramParameteriEXT( iProgramID, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP );
+
+ int ierror, i;
+ GLint imaxvert;
+ glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT,&imaxvert);
+ if( (ierror = glGetError()) != 0 )
+ {
+ puts( "ERROR: You cannot load a geometry shader when there are still errors left in OpenGL." );
+ puts( "Please track down the error remaining by using glGetError() to cordon off your code." );
+ printf( "The last error received was: %d\n", ierror );
+ }
+ for( i = 1; i < imaxvert; i++ )
+ {
+ glProgramParameteriEXT(iProgramID,GL_GEOMETRY_VERTICES_OUT_EXT,imaxvert/i);
+ if( glGetError() == 0 )
+ break;
+ }
+ printf( "Geometry Shader loaded with a total of %d max verticies. Because there are %d max vertices, and %d preceived components per vert.\n", imaxvert/i, imaxvert, i );
+ }
+
+
+ //See if there were any errors.
+ glGetObjectParameterivARB( iProgramID, GL_OBJECT_LINK_STATUS_ARB, &bLinked );
+ glGetObjectParameterivARB( iProgramID, GL_OBJECT_INFO_LOG_LENGTH_ARB, &stringLength );
+
+ if ( stringLength > 1 || bLinked == 0 )
+ {
+ char * tmpstr = (char*)malloc( stringLength + 1 );
+ glGetInfoLogARB( iProgramID, stringLength, NULL, tmpstr );
+ puts( "Linking shaders. response follows:" );
+ puts( tmpstr );
+ free( tmpstr );
+ return bLinked!=0;
+ }
+
+ //Build the list of uniform tabs.
+ int iNumUniforms;
+ glGetObjectParameterivARB( iProgramID, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &iNumUniforms );
+ m_vShaderTabs.resize( iNumUniforms );
+ for( int i = 0; i < iNumUniforms; ++i )
+ {
+ char buffer[1024];
+ int bufflen;
+ GLint size;
+ GLenum type;
+ glGetActiveUniformARB( iProgramID, i, 1024, &bufflen, &size, &type, buffer );
+ buffer[bufflen] = 0;
+ m_vShaderTabs[i] = SHADERATTRIBUTES.GetHandle( buffer );
+ }
+ return true;
+}
+
+void Shader::DestroyShader()
+{
+ GLhandleARB *objects = NULL;
+ GLint i, count = -1;
+
+ if( !iProgramID )
+ return;
+
+ //If we can't destroy the object, then don't try.
+ glGetObjectParameterivARB(iProgramID, GL_OBJECT_ATTACHED_OBJECTS_ARB, &count);
+
+ //Iterate through all children.
+ if (count > 0)
+ {
+ objects = (GLhandleARB *)malloc(count*sizeof(GLhandleARB));
+ glGetAttachedObjectsARB(iProgramID, count, NULL, objects);
+ }
+ else
+ return;
+
+ for ( i = 0; i < count; ++i)
+ {
+ glDetachObjectARB(iProgramID, objects[i]);
+ }
+
+ glDeleteObjectARB(iProgramID);
+ free( objects );
+ return;
+}
+
+void Shader::CheckForNewer( )
+{
+ unsigned long iCurTimes[3];
+ GetTimeCodes( iCurTimes );
+ if( iCurTimes[0] != iTimeCode[0] || iCurTimes[1] != iTimeCode[1] || iCurTimes[2] != iTimeCode[2] )
+ {
+ DestroyShader( );
+ LoadShader( );
+ }
+}
+
+void Shader::GetTimeCodes( unsigned long * iOut )
+{
+ MercuryFile * f = FILEMAN.Open( sShaderName + ".frag" );
+ iOut[0] = f->GetModTime();
+ f->Close();
+
+ f = FILEMAN.Open( sShaderName + ".vert" );
+ iOut[1] = f->GetModTime();
+ f->Close();
+
+ f = FILEMAN.Open( sShaderName + ".geom" );
+ iOut[2] = f->GetModTime();
+ f->Close();
+}
+
+void Shader::ActivateShader()
+{
+ glUseProgramObjectARB( iProgramID );
+
+ for( unsigned i = 0; i < m_vShaderTabs.size(); ++i )
+ {
+ ShaderAttribute * sa = m_vShaderTabs[i];
+ switch( sa->typ )
+ {
+ case ShaderAttribute::TYPE_INT:
+ case ShaderAttribute::TYPE_SAMPLER:
+ glUniform1iARB( i, sa->sau.iInt );
+ break;
+ case ShaderAttribute::TYPE_FLOAT:
+ case ShaderAttribute::TYPE_FLOATV4:
+ glUniform4fvARB( i, 4, &sa->sau.fFloatV4[0] );
+ };
+ }
+}
+
+void Shader::DeactivateShader()
+{
+ glUseProgramObjectARB( 0 );
+}
+
+
+/*
+ * Copyright (c) 2009 Charles Lohr
+ * 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 the Mercury Engine 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.
+ */
Added: Mercury2/src/Shader.h
===================================================================
--- Mercury2/src/Shader.h (rev 0)
+++ Mercury2/src/Shader.h 2009-03-20 03:37:43 UTC (rev 187)
@@ -0,0 +1,185 @@
+#ifndef SHADER_H
+#define SHADER_H
+
+#include <MercuryAsset.h>
+#include <map>
+#include <vector>
+
+///Basic Attribute for all shaders
+class ShaderAttribute
+{
+public:
+ ShaderAttribute() : typ( TYPE_INT ) { sau.iInt = 0; }
+
+ ///Type of ShaderAttribute for shader
+ enum ShaderAttributeTyp
+ {
+ TYPE_INT, ///Synonomous to 'int' when passing into a shader
+ TYPE_SAMPLER, ///Synonomous to 'sampler2D' when passing into a shader
+ TYPE_FLOAT, ///Synonomous to 'float' when passing into a shader
+ TYPE_FLOATV4 ///Synonomous to 'vec4' when passing into a shader
+ } typ;
+
+ ///Actual data for value.
+ union ShaderAttributeVal
+ {
+ int iInt; ///Synonomous to 'int'
+ unsigned int iSampler; ///Synonomous to 'sampler2D'
+ float fFloat; ///Synonomous to 'float'
+ float fFloatV4[4]; ///Synonomous to 'vec4'
+ } sau;
+};
+
+///Shader Attribute Retainer
+class ShaderAttributesSet
+{
+public:
+ ShaderAttribute * GetHandle( const MString & sName );
+private:
+ ///All shader attributes
+ /** This contains a list of all attributes that are passed in.
+ If a shader does not have an attribute, it does not insert
+ it here. Note that if shaders change and an element is
+ no longer referenced, it will remain in the map, as to
+ prevent bad pointers. This list is all-enduring.
+ */
+ std::map< MString, ShaderAttribute * > m_AllShaderAttributes;
+};
+
+extern ShaderAttributesSet SHADERATTRIBUTES;
+
+///Basic element for turning shaders on and off
+/** This class helps aide in the loading and use of shaders. It allows loading of files
+ through the LoadShader() function that actively looks for .frag and .vert files. By use
+ of the CheckForNewer() function, the class looks for changes, if any changes are found
+ the shaders will be re-loaded, compiled and linked. Once this node has a shader loaded,
+ the shader can be activated or deactivated using the ActivateShader() and
+ DeactivateShader() functions respectively. Note that shaders do not stack. When you
+ call DeactiveShader(), it does not bring back previously activated shaders. It simply
+ turns all shaders off. */
+class Shader : public MercuryAsset
+{
+public:
+ Shader();
+ virtual ~Shader();
+
+ virtual void Init(MercuryNode* node);
+ virtual void Render(const MercuryNode* node);
+ virtual void PostRender(const MercuryNode* node);
+ static Shader* Generate() { return new Shader; }
+ virtual void LoadFromXML(const XMLNode& node);
+
+ ///Explicitly get the OpenGL ProgramID in the event you need it for advanced techniques
+ unsigned int GetProgramID() { return iProgramID; }
+private:
+ ///Suggested function for loading shaders.
+ /** This function looks for {sShaderName}.vert and {sShaderName}.frag. It will
+ attempt to load, compile and link the files. If any errors are found, they will
+ be announced at STDOUT. When Geometry shader support is added, it will search
+ for .geom files */
+ bool LoadShader( );
+
+ ///Explicitly load a fragment shader
+ /** This function takes on raw code in the sShaderCode string and attemps to compile
+ it. Any errors it runs into will be displayed at STDOUT. Note you are
+ discouraged to use this function, in favor of using LoadShader(). */
+ bool LoadShaderFrag( const char * sShaderCode );
+
+ ///Explicitly load a vertex shader
+ /** This function takes on raw code in the sShaderCode string and attemps to compile
+ it. Any errors it runs into will be displayed at STDOUT. You should use
+ LoadShader() instead of this function. */
+ bool LoadShaderVert( const char * sShaderCode );
+
+ ///Explicitly load a geometry shader
+ /** This function takes on raw code in the sShaderCode string and attemps to compile
+ it. Any errors it runs into will be displayed at STDOUT. You should use
+ LoadShader() instead of this function. */
+ bool LoadShaderGeom( const char * sShaderCode );
+
+ ///Explicitly link all shaders currently loaded
+ /** This takes vertexShader and fragmentShader and converts them into iProgramID.
+ this function is discouraged when using LoadShader(). */
+ bool LinkShaders();
+
+ ///Check for newer version of 'this' shader
+ void CheckForNewer( );
+
+ ///Get the last modified time for sShaderName
+ /* This function takes on iOut as being where to put the last time the shader was modified.
+ this value is system dependent and is necessiarly not linked to anything like seconds. */
+ void GetTimeCodes( unsigned long * iOut );
+
+ ///Activate Shader (apply to current OpenGL state)
+ void ActivateShader();
+
+ ///Turn all shaders off in OpenGL state.
+ void DeactivateShader();
+
+ ///Destroy this shader
+ void DestroyShader();
+
+ ///The last time codes (for vertex and fragment shaders respectively)
+ unsigned long iTimeCode[3];
+
+ ///The OpenGL Program ID (in OpenGL Land, contains all shaders, linked together)
+ unsigned int iProgramID;
+
+ ///The OpenGL Geometry Program ID
+ unsigned int geometryShader;
+
+ ///The OpenGL Vertex Program ID
+ unsigned int vertexShader;
+
+ ///The OpenGL Fragment Program ID
+ unsigned int fragmentShader;
+
+ ///Shader attributes
+ /** This is the system that helps make it possible to blast
+ through all attributes currently set up by dereferencing
+ the pointers in the attributes repository.
+ */
+ std::vector< ShaderAttribute * > m_vShaderTabs;
+
+ ///Name of the shader
+ MString sShaderName;
+
+ ///Priority of THIS shader (if lower than currently active one, it does not become active)
+ float fPriority;
+
+ ///Global static shader (so shaders can recursively activate and deactivate shaders)
+ static Shader * CurrentShader;
+
+ ///Original Shader (to re-enable when leaving)
+ Shader * OriginalShader;
+};
+
+#endif
+
+/*
+ * Copyright (c) 2009 Charles Lohr
+ * 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 the Mercury Engine 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.
+ */
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|