/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2004 Robert Osfield 
 * Copyright (C) 2003-2004 3Dlabs Inc. Ltd.
 *
 * This application is open source and may be redistributed and/or modified   
 * freely and without restriction, both in commericial and non commericial
 * applications, as long as this copyright notice is maintained.
 * 
 * This application is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/

/* file:	include/osgGL2/ProgramObject
 * author:	Mike Weiblen 2004-11-09
 *
 * See http://www.3dlabs.com/opengl2/ for more information regarding
 * the OpenGL Shading Language.
*/


#ifndef OSGGL2_PROGRAMOBJECT
#define OSGGL2_PROGRAMOBJECT 1

#include <osg/State>
#include <osg/StateAttribute>
#include <osg/buffered_value>
#include <osg/ref_ptr>
#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Vec4>

#include <osgGL2/Export>
#include <osgGL2/Extensions>
#include <osgGL2/UniformValue>

#include <string>
#include <vector>


namespace osgGL2 {

class ProgramObject;
typedef osg::ref_ptr<ProgramObject> ProgramObjectPtr;

class ShaderObject;
typedef osg::ref_ptr<ShaderObject> ShaderObjectPtr;

///////////////////////////////////////////////////////////////////////////
/** osgGL2::ProgramObject is an application-level abstraction of the OpenGL Shading Language glProgramObject.
  * It is an osg::StateAttribute that, when applied, will install an OGLSL
  * shader program for subsequent rendering.
  * osgGL2::ShaderObjects containing the actual shader source code are
  * attached to the ProgramObject, which will then manage the compilation,
  * linking, and installation of the GL shader program.
  * ProgramObject will automatically manage per-context instancing of the
  * internal objects, if that is necessary for a particular display
  * configuration.
  */

class OSGGL2_EXPORT ProgramObject : public osg::StateAttribute
{
    public:
        ProgramObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        ProgramObject(const ProgramObject& rhs, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_StateAttribute(osgGL2, ProgramObject, PROGRAMOBJECT);

        /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
        virtual int compare(const osg::StateAttribute& sa) const;

	/** If enabled, install our shader program in the GL pipeline,
	  * performing any shader program rebuild operations that might
	  * be pending. */
	virtual void apply(osg::State& state) const;

	virtual void compileGLObjects(osg::State& state) const;

        /** release an OpenGL objects in specified graphics context if State
            object is passed, otherwise release OpenGL objexts for all graphics context if
            State object pointer NULL.*/
        virtual void releaseGLObjects(osg::State* state=0) const;

        // data access methods.

	/** Mark us as "dirty" and in need of relinking. */
	void dirtyProgramObject();

	/** Mark our attached ShaderObjects as "dirty" and in need of
	  * recompilation. */
	void dirtyShaderObjects();

	/** An override to control whether the shader program will
	  * actually be installed when OSG attempts to apply() */
	void enable( bool enabled ) { _enabled = enabled; }

	/** Attach a ShaderObject to this ProgramObject */
	void addShader( ShaderObject* shadObj );

	/** Assign a value to a ProgramObject's uniform variable */
	void setUniform( const char* uniformName, int value );
	void setUniform( const char* uniformName, float value );
	void setUniform( const char* uniformName, osg::Vec2 value );
	void setUniform( const char* uniformName, osg::Vec3 value );
	void setUniform( const char* uniformName, osg::Vec4 value );

	inline void setSampler( const char* uniformName, int value )
	{
	    // emphatic alias for setUniform(int)
	    setUniform( uniformName, static_cast<int>(value) );
	}
        
        /** Mark internal GL objects for deletion.
	  * Deletion requests are queued until they can be executed
	  * in the proper GL context. */
        static void deleteObject(unsigned int contextID, GLhandleARB handle);

        /** flush all the cached glProgramObjects which need to be deleted
          * in the OpenGL context related to contextID.*/
        static void flushDeletedGL2Objects(unsigned int contextID,double currentTime, double& availableTime);


    protected:
	/** PCPO is an OSG-internal encapsulation of glProgramObjects per-GL context.  */
	class PerContextProgObj : public osg::Referenced
	{
	    public:
		PerContextProgObj(const ProgramObject* progObj, unsigned int contextID);
		PerContextProgObj(const PerContextProgObj& rhs);

		GLhandleARB& getHandle() {return _glProgObjHandle;}

		bool isDirty() const {return _dirty;}
		void markAsDirty() {_dirty = true; }
		void build();
		void use() const;

		/** Add a list of UniformValues to our per-context queue */
		void updateUniforms( const UniformValueList& univalList );

		/** Apply our queue of pending UniformValue updates to the glProgramObjects */
		void applyUniformValues();

	    protected:	/*methods*/
		PerContextProgObj();
		~PerContextProgObj();

	    protected:	/*data*/
		/** Pointer to our parent ProgramObject */
		const ProgramObject* _progObj;
		/** Pointer to this context's extension functions */
		osg::ref_ptr<Extensions> _extensions;
		/** Handle to the actual glProgramObject */
		GLhandleARB _glProgObjHandle;
		/** Do we need to be linked? */
		bool _dirty;
		/** Queue of UniformValues awaiting assignment */
		UniformValueList _univalList;
		const unsigned int _contextID;
	};

    protected:	/*methods*/
        virtual ~ProgramObject();
	/** Get the PCPO for a particular GL context */
        PerContextProgObj* getPCPO(unsigned int contextID) const;

	/** Per frame, copy the list of pending UniformValue updates to
	  * each of the PCPOs. */
	void updateUniforms( int frameNumber ) const;

    protected:	/*data*/
	bool _enabled;
        
        typedef std::vector< ShaderObjectPtr > ShaderObjectList;
	ShaderObjectList _shaderObjectList;
        mutable osg::buffered_value< osg::ref_ptr<PerContextProgObj> > _pcpoList;
	mutable int _frameNumberOfLastPCPOUpdate;
	mutable UniformValueList _univalList;

    private:
        const ProgramObject& operator=(const ProgramObject&);
};

///////////////////////////////////////////////////////////////////////////
/** osgGL2::ShaderObject is an application-level abstraction of the OpenGL Shading Language glShaderObject.
  * It is a container to load the shader source code text and manage its
  * compilation.
  * A ShaderObject may be attached to more than one osgGL2::ProgramObject.
  * ShaderObject will automatically manage per-context instancing of the
  * internal objects, if that is necessary for a particular display
  * configuration.
  */

class OSGGL2_EXPORT ShaderObject : public osg::Object
{
    public:

	enum Type {
	    VERTEX = GL_VERTEX_SHADER_ARB,
	    FRAGMENT = GL_FRAGMENT_SHADER_ARB,
	    UNKNOWN = -1
	};

        ShaderObject();
        ShaderObject(Type type);
        ShaderObject(Type type, const char* sourceText);

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        ShaderObject(const ShaderObject& rhs, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
        META_Object(osgGL2, ShaderObject);

        int compare(const ShaderObject& sa) const;

        // data access methods.

	/** Load the ShaderObject's source code text from a string. */
	void setShaderSource( const char* sourceText );

	/** Retreive the source code text */
	inline const std::string& getShaderSource() const {return _shaderSource; }

	/** Load the ShaderObject's source code text from a file. */
	bool loadShaderSourceFromFile( const char* fileName );

	/** Get the ShaderObject type as an enum. */
	inline Type getType() const { return _type; }

	/** Get the ShaderObject type as a descriptive string. */
	const char* getTypename() const;

        /** Mark us as "dirty" and in need of recompilation */
        void dirtyShaderObject();        

	/** Perform a recompilation of all our PCSOs */
	void build(unsigned int contextID) const;

	/** For a given GL context, attach a glShaderObject to a glProgramObject */
	void attach(unsigned int contextID, GLhandleARB progObj) const;


    protected:
	/** PCSO is an OSG-internal encapsulation of glShaderObjects per-GL context.  */
	class PerContextShaderObj : public osg::Referenced
	{
	    public:
		PerContextShaderObj(const ShaderObject* shadObj, unsigned int contextID);
		PerContextShaderObj(const PerContextShaderObj& rhs);

		GLhandleARB& getHandle() {return _glShaderObjHandle;}

		bool isDirty() const {return _dirty;}
		void markAsDirty() {_dirty = true; }
		void build();

		/** Attach our glShaderObject to a glProgramObject */
		void attach(GLhandleARB progObj) const;

	    protected:	/*methods*/
		PerContextShaderObj();
		~PerContextShaderObj();

	    protected:	/*data*/
		/** Pointer to our parent ShaderObject */
		const ShaderObject* _shadObj;
		/** Pointer to this context's extension functions. */
		osg::ref_ptr<Extensions> _extensions;
		/** Handle to the actual glShaderObject. */
		GLhandleARB _glShaderObjHandle;
		/** Do we need to be recompiled? */
		bool _dirty;
		const unsigned int _contextID;
	};

    protected:	/*methods*/
        virtual ~ShaderObject();
        PerContextShaderObj* getPCSO(unsigned int contextID) const;

	friend void ProgramObject::addShader( ShaderObject* shadObj );	// to access addProgObjRef()
	void addProgObjRef( ProgramObject* progObj );

    protected:	/*data*/
	Type _type;
	std::string _shaderSource;
	std::vector< ProgramObjectPtr > _programObjectList;
        mutable osg::buffered_value< osg::ref_ptr<PerContextShaderObj> > _pcsoList;

    private:
        const ShaderObject& operator=(const ShaderObject&);
};

}

#endif

/*EOF*/
