From Michael Platings, I've added initial support to osg for glGetProgramBinary and glProgramBinary. This means that shader programs can now be cached to disk and later reloaded, which is much faster than linking shaders from source code. This should mean significantly shorter load times for people who use lots of combinations of shaders.

This commit is contained in:
Robert Osfield 2011-02-14 12:54:21 +00:00
parent 597f978128
commit 1d55efb721
5 changed files with 266 additions and 54 deletions

View File

@ -284,6 +284,13 @@ typedef char GLchar;
#define GL_INVALID_INDEX 0xFFFFFFFFu
#endif
//ARB_get_program_binary
#ifndef GL_PROGRAM_BINARY_RETRIEVABLE_HINT
#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
#define GL_PROGRAM_BINARY_LENGTH 0x8741
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#define GL_PROGRAM_BINARY_FORMATS 0x87FF
#endif
namespace osg {
@ -326,6 +333,10 @@ class OSG_EXPORT GL2Extensions : public osg::Referenced
void setUniformBufferObjectSupported(bool flag) { _isUniformBufferObjectSupported = flag; }
bool isUniformBufferObjectSupported() {return _isUniformBufferObjectSupported; }
void setGetProgramBinarySupported(bool flag) { _isGetProgramBinarySupported = flag; }
bool isGetProgramBinarySupported() {return _isGetProgramBinarySupported; }
/** Function to call to get the extension of a specified context.
* If the Exentsion object for that context has not yet been created then
* and the 'createIfNotInitalized' flag been set to false then returns NULL.
@ -480,6 +491,11 @@ class OSG_EXPORT GL2Extensions : public osg::Referenced
void glGetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params) const;
void glGetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName) const;
void glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) const;
// ARB_get_program_binary
void glGetProgramBinary(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary) const;
void glProgramBinary(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length) const;
protected:
~GL2Extensions() {}
@ -494,6 +510,7 @@ class OSG_EXPORT GL2Extensions : public osg::Referenced
bool _areTessellationShadersSupported;
bool _isGpuShader4Supported;
bool _isUniformBufferObjectSupported;
bool _isGetProgramBinarySupported;
typedef void (GL_APIENTRY * BlendEquationSeparateProc)(GLenum modeRGB, GLenum modeAlpha);
typedef void (GL_APIENTRY * DrawBuffersProc)(GLsizei n, const GLenum *bufs);
@ -622,6 +639,8 @@ class OSG_EXPORT GL2Extensions : public osg::Referenced
typedef void (GL_APIENTRY * GetActiveUniformBlockivProc)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params);
typedef void (GL_APIENTRY * GetActiveUniformBlockNameProc)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName);
typedef void (GL_APIENTRY * UniformBlockBindingProc)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
typedef void (GL_APIENTRY * GetProgramBinaryProc)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary);
typedef void (GL_APIENTRY * ProgramBinaryProc)(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length);
BlendEquationSeparateProc _glBlendEquationSeparate;
DrawBuffersProc _glDrawBuffers;
@ -761,6 +780,10 @@ class OSG_EXPORT GL2Extensions : public osg::Referenced
GetActiveUniformBlockivProc _glGetActiveUniformBlockiv;
GetActiveUniformBlockNameProc _glGetActiveUniformBlockName;
UniformBlockBindingProc _glUniformBlockBinding;
//ARB_get_program_binary
GetProgramBinaryProc _glGetProgramBinary;
ProgramBinaryProc _glProgramBinary;
};
}

View File

@ -35,6 +35,51 @@ namespace osg {
class State;
/** Simple class for wrapping up the data used in glProgramBinary and glGetProgramBinary.
* On the first run of your application Programs should be assigned an empty ProgramBinary.
* Before your application exits it should retrieve the program binary via
* Program::PerContextProgram::compileProgramBinary and save it to disk.
* When your application is run subsequently, load your binary from disk and use it to set
* the data of a ProgramBinary, and set the ProgramBinary on the associated Program.
* This will typically result in Program::compileGLObjects executing much faster.*/
class OSG_EXPORT ProgramBinary : public osg::Object
{
public:
ProgramBinary();
/** Copy constructor using CopyOp to manage deep vs shallow copy.*/
ProgramBinary(const ProgramBinary& rhs, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
META_Object(osg, ProgramBinary);
/** Allocated a data buffer of specified size/*/
void allocate(unsigned int size);
/** Assign program binary data, copying the specified data into locally stored data buffer, the original data can then be deleted.*/
void assign(unsigned int size, const unsigned char* data);
/** Set the format of the program binary data.*/
void setFormat(GLenum format) {_format = format;}
/** Get the format of the program binary data.*/
GLenum getFormat() const {return _format;}
/** Get the size of the program binary data.*/
unsigned int getSize() const { return _data.size(); }
/** Get a ptr to the program binary data.*/
unsigned char* getData() { return _data.empty() ? 0 : &(_data.front()); }
/** Get a const ptr to the program binary data.*/
const unsigned char* getData() const { return _data.empty() ? 0 : &(_data.front()); }
protected:
std::vector<unsigned char> _data;
GLenum _format;
};
///////////////////////////////////////////////////////////////////////////
/** osg::Program is an application-level abstraction of an OpenGL glProgram.
* It is an osg::StateAttribute that, when applied, will activate a
@ -122,6 +167,17 @@ class OSG_EXPORT Program : public osg::StateAttribute
/** Remove a uniform block binding. */
void removeBindUniformBlock(const std::string& name);
/** Set the Program using a ProgramBinary. If a ProgramBinary is not yet
* available then setting an empty one signals that compileProgramBinary
* will be called later.*/
void setProgramBinary(ProgramBinary* programBinary) { _programBinary = programBinary; }
/** Get the Program's ProgramBinary, return NULL if none is assigned. */
ProgramBinary* getProgramBinary() { return _programBinary.get(); }
/** Get the const Program's ProgramBinary, return NULL if none is assigned. */
const ProgramBinary* getProgramBinary() const { return _programBinary.get(); }
typedef std::map<std::string,GLuint> AttribBindingList;
typedef std::map<std::string,GLuint> FragDataBindingList;
typedef std::map<std::string,GLuint> UniformBlockBindingList;
@ -196,6 +252,17 @@ class OSG_EXPORT Program : public osg::StateAttribute
bool isLinked() const {return _isLinked;}
bool getInfoLog( std::string& infoLog ) const;
/** Was glProgramBinary called successfully? */
bool loadedBinary() const {return _loadedBinary;}
/** Compile a program binary. For this to work setProgramBinary must have
* been called on the osg::Program with an empty ProgramBinary prior to
* compileGLObjects being called.
* compileProgramBinary should be called after the program has been
* "exercised" by rendering with it. The ProgramBinary can then be saved
* to disk for faster subsequent compiling. */
ProgramBinary* compileProgramBinary(osg::State& state);
void useProgram() const;
void resetAppliedUniforms() const
@ -275,6 +342,8 @@ class OSG_EXPORT Program : public osg::StateAttribute
bool _needsLink;
/** Is our glProgram successfully linked? */
bool _isLinked;
/** Was glProgramBinary called successfully? */
bool _loadedBinary;
const unsigned int _contextID;
ActiveUniformMap _uniformInfoMap;
@ -311,6 +380,8 @@ class OSG_EXPORT Program : public osg::StateAttribute
typedef std::vector< ref_ptr<Shader> > ShaderList;
ShaderList _shaderList;
osg::ref_ptr<ProgramBinary> _programBinary;
/** Parameters maintained with glProgramParameteriEXT */
GLint _geometryVerticesOut;
GLint _geometryInputType;

View File

@ -17,10 +17,7 @@
#include <osg/Object>
#include <osg/StateAttribute>
#include <osg/ref_ptr>
#include <osg/Uniform>
#include <osg/Program>
#include <map>
#include <vector>

View File

@ -48,6 +48,7 @@ GL2Extensions::GL2Extensions(const GL2Extensions& rhs) : osg::Referenced()
_isGeometryShader4Supported = rhs._isGeometryShader4Supported;
_isGpuShader4Supported = rhs._isGpuShader4Supported;
_isUniformBufferObjectSupported = rhs._isUniformBufferObjectSupported;
_isGetProgramBinarySupported = rhs._isGetProgramBinarySupported;
_glBlendEquationSeparate = rhs._glBlendEquationSeparate;
_glDrawBuffers = rhs._glDrawBuffers;
@ -183,6 +184,10 @@ GL2Extensions::GL2Extensions(const GL2Extensions& rhs) : osg::Referenced()
_glGetActiveUniformBlockiv = rhs._glGetActiveUniformBlockiv;
_glGetActiveUniformBlockName = rhs._glGetActiveUniformBlockName;
_glUniformBlockBinding = rhs._glUniformBlockBinding;
// ARB_get_program_binary
_glGetProgramBinary = rhs._glGetProgramBinary;
_glProgramBinary = rhs._glProgramBinary;
}
@ -338,6 +343,9 @@ void GL2Extensions::lowestCommonDenominator(const GL2Extensions& rhs)
if (!rhs._glGetActiveUniformBlockName) _glGetActiveUniformBlockName = 0;
if (!rhs._glUniformBlockBinding) _glUniformBlockBinding = 0;
// ARB_get_program_binary
if (!rhs._glGetProgramBinary) _glGetProgramBinary = 0;
if (!rhs._glProgramBinary) _glProgramBinary = 0;
}
@ -362,7 +370,8 @@ void GL2Extensions::setupGL2Extensions(unsigned int contextID)
_isGeometryShader4Supported = osg::isGLExtensionSupported(contextID,"GL_EXT_geometry_shader4");
_isGpuShader4Supported = osg::isGLExtensionSupported(contextID,"GL_EXT_gpu_shader4");
_areTessellationShadersSupported = osg::isGLExtensionSupported(contextID, "GL_ARB_tessellation_shader");
_isUniformBufferObjectSupported = osg::isGLExtensionSupported(contextID,"ARB_uniform_buffer_object");
_isUniformBufferObjectSupported = osg::isGLExtensionSupported(contextID,"GL_ARB_uniform_buffer_object");
_isGetProgramBinarySupported = osg::isGLExtensionSupported(contextID,"GL_ARB_get_program_binary");
if( isGlslSupported() )
{
@ -520,7 +529,9 @@ void GL2Extensions::setupGL2Extensions(unsigned int contextID)
setGLExtensionFuncPtr(_glGetActiveUniformBlockiv, "glGetActiveUniformBlockiv");
setGLExtensionFuncPtr(_glGetActiveUniformBlockName, "glGetActiveUniformBlockName");
setGLExtensionFuncPtr(_glUniformBlockBinding, "glUniformBlockBinding");
//ARB_get_program_binary
setGLExtensionFuncPtr(_glGetProgramBinary, "glGetProgramBinary");
setGLExtensionFuncPtr(_glProgramBinary, "glProgramBinary");
}
@ -2287,6 +2298,38 @@ void GL2Extensions::glUniformBlockBinding(GLuint program,
}
}
//ARB_get_program_binary
void GL2Extensions::glGetProgramBinary(GLuint program,
GLsizei bufSize,
GLsizei *length,
GLenum *binaryFormat,
GLvoid *binary) const
{
if (_glGetProgramBinary)
{
_glGetProgramBinary(program, bufSize, length, binaryFormat, binary);
}
else
{
NotSupported("glGetProgramBinary");
}
}
void GL2Extensions::glProgramBinary(GLuint program,
GLenum binaryFormat,
const GLvoid *binary,
GLsizei length) const
{
if (_glProgramBinary)
{
_glProgramBinary(program, binaryFormat, binary, length);
}
else
{
NotSupported("glProgramBinary");
}
}
///////////////////////////////////////////////////////////////////////////
// C++-friendly convenience methods

View File

@ -20,6 +20,7 @@
*/
#include <list>
#include <fstream>
#include <osg/Notify>
#include <osg/State>
@ -93,6 +94,38 @@ void Program::discardDeletedGlPrograms(unsigned int contextID)
}
///////////////////////////////////////////////////////////////////////////
// osg::ProgramBinary
///////////////////////////////////////////////////////////////////////////
ProgramBinary::ProgramBinary() : _format(0)
{
}
ProgramBinary::ProgramBinary(const ProgramBinary& rhs, const osg::CopyOp&) :
_data(rhs._data), _format(rhs._format)
{
}
void ProgramBinary::allocate(unsigned int size)
{
_data.clear();
_data.resize(size);
}
void ProgramBinary::assign(unsigned int size, const unsigned char* data)
{
allocate(size);
if (data)
{
for(unsigned int i=0; i<size; ++i)
{
_data[i] = data[i];
}
}
}
///////////////////////////////////////////////////////////////////////////
// osg::Program
///////////////////////////////////////////////////////////////////////////
@ -240,7 +273,6 @@ void Program::releaseGLObjects(osg::State* state) const
}
}
bool Program::addShader( Shader* shader )
{
if( !shader ) return false;
@ -336,14 +368,14 @@ void Program::setParameterfv( GLenum pname, const GLfloat* value )
const GLfloat* Program::getParameterfv( GLenum pname ) const
{
switch( pname )
/*switch( pname )
{
;
// todo tessellation default level
// case GL_PATCH_DEFAULT_INNER_LEVEL: return _patchDefaultInnerLevel;
// case GL_PATCH_DEFAULT_OUTER_LEVEL: return _patchDefaultOuterLevel;
}
}*/
OSG_WARN << "getParameter invalid param " << pname << std::endl;
return 0;
}
@ -484,7 +516,8 @@ const Program::ActiveVarInfoMap& Program::getActiveAttribs(unsigned int contextI
Program::PerContextProgram::PerContextProgram(const Program* program, unsigned int contextID ) :
osg::Referenced(),
_contextID( contextID )
_contextID( contextID ),
_loadedBinary(false)
{
_program = program;
_extensions = GL2Extensions::Get( _contextID, true );
@ -515,30 +548,48 @@ void Program::PerContextProgram::linkProgram(osg::State& state)
<< " contextID=" << _contextID
<< std::endl;
if (_extensions->isGeometryShader4Supported())
const ProgramBinary* programBinary = _program->getProgramBinary();
_loadedBinary = false;
if (programBinary && programBinary->getSize())
{
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_VERTICES_OUT_EXT, _program->_geometryVerticesOut );
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_INPUT_TYPE_EXT, _program->_geometryInputType );
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_OUTPUT_TYPE_EXT, _program->_geometryOutputType );
GLint linked = GL_FALSE;
_extensions->glProgramBinary( _glProgramHandle, programBinary->getFormat(),
reinterpret_cast<const GLvoid*>(programBinary->getData()), programBinary->getSize() );
_extensions->glGetProgramiv( _glProgramHandle, GL_LINK_STATUS, &linked );
_loadedBinary = _isLinked = (linked == GL_TRUE);
}
if (_extensions->areTessellationShadersSupported() )
if (!_loadedBinary)
{
_extensions->glPatchParameteri( GL_PATCH_VERTICES, _program->_patchVertices );
// todo: add default tessellation level
}
if (_extensions->isGeometryShader4Supported())
{
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_VERTICES_OUT_EXT, _program->_geometryVerticesOut );
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_INPUT_TYPE_EXT, _program->_geometryInputType );
_extensions->glProgramParameteri( _glProgramHandle, GL_GEOMETRY_OUTPUT_TYPE_EXT, _program->_geometryOutputType );
}
// Detach removed shaders
for( unsigned int i=0; i < _shadersToDetach.size(); ++i )
{
_shadersToDetach[i]->detachShader( _contextID, _glProgramHandle );
if (_extensions->areTessellationShadersSupported() )
{
_extensions->glPatchParameteri( GL_PATCH_VERTICES, _program->_patchVertices );
// todo: add default tessellation level
}
// Detach removed shaders
for( unsigned int i=0; i < _shadersToDetach.size(); ++i )
{
_shadersToDetach[i]->detachShader( _contextID, _glProgramHandle );
}
}
_shadersToDetach.clear();
// Attach new shaders
for( unsigned int i=0; i < _shadersToAttach.size(); ++i )
if (!_loadedBinary)
{
_shadersToAttach[i]->attachShader( _contextID, _glProgramHandle );
// Attach new shaders
for( unsigned int i=0; i < _shadersToAttach.size(); ++i )
{
_shadersToAttach[i]->attachShader( _contextID, _glProgramHandle );
}
}
_shadersToAttach.clear();
@ -546,41 +597,51 @@ void Program::PerContextProgram::linkProgram(osg::State& state)
_attribInfoMap.clear();
_lastAppliedUniformList.clear();
// set any explicit vertex attribute bindings
const AttribBindingList& programBindlist = _program->getAttribBindingList();
for( AttribBindingList::const_iterator itr = programBindlist.begin();
itr != programBindlist.end(); ++itr )
if (!_loadedBinary)
{
OSG_INFO<<"Program's vertex attrib binding "<<itr->second<<", "<<itr->first<<std::endl;
_extensions->glBindAttribLocation( _glProgramHandle, itr->second, reinterpret_cast<const GLchar*>(itr->first.c_str()) );
}
// set any explicit vertex attribute bindings that are set up via osg::State, such as the vertex arrays
// that have been aliase to vertex attrib arrays
if (state.getUseVertexAttributeAliasing())
{
const AttribBindingList& stateBindlist = state.getAttributeBindingList();
for( AttribBindingList::const_iterator itr = stateBindlist.begin();
itr != stateBindlist.end(); ++itr )
// set any explicit vertex attribute bindings
const AttribBindingList& programBindlist = _program->getAttribBindingList();
for( AttribBindingList::const_iterator itr = programBindlist.begin();
itr != programBindlist.end(); ++itr )
{
OSG_INFO<<"State's vertex attrib binding "<<itr->second<<", "<<itr->first<<std::endl;
OSG_INFO<<"Program's vertex attrib binding "<<itr->second<<", "<<itr->first<<std::endl;
_extensions->glBindAttribLocation( _glProgramHandle, itr->second, reinterpret_cast<const GLchar*>(itr->first.c_str()) );
}
// set any explicit vertex attribute bindings that are set up via osg::State, such as the vertex arrays
// that have been aliase to vertex attrib arrays
if (state.getUseVertexAttributeAliasing())
{
const AttribBindingList& stateBindlist = state.getAttributeBindingList();
for( AttribBindingList::const_iterator itr = stateBindlist.begin();
itr != stateBindlist.end(); ++itr )
{
OSG_INFO<<"State's vertex attrib binding "<<itr->second<<", "<<itr->first<<std::endl;
_extensions->glBindAttribLocation( _glProgramHandle, itr->second, reinterpret_cast<const GLchar*>(itr->first.c_str()) );
}
}
// set any explicit frag data bindings
const FragDataBindingList& fdbindlist = _program->getFragDataBindingList();
for( FragDataBindingList::const_iterator itr = fdbindlist.begin();
itr != fdbindlist.end(); ++itr )
{
_extensions->glBindFragDataLocation( _glProgramHandle, itr->second, reinterpret_cast<const GLchar*>(itr->first.c_str()) );
}
// if any program binary has been set then assume we want to retrieve a binary later.
if (programBinary)
{
_extensions->glProgramParameteri( _glProgramHandle, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE );
}
// link the glProgram
GLint linked = GL_FALSE;
_extensions->glLinkProgram( _glProgramHandle );
_extensions->glGetProgramiv( _glProgramHandle, GL_LINK_STATUS, &linked );
_isLinked = (linked == GL_TRUE);
}
// set any explicit frag data bindings
const FragDataBindingList& fdbindlist = _program->getFragDataBindingList();
for( FragDataBindingList::const_iterator itr = fdbindlist.begin();
itr != fdbindlist.end(); ++itr )
{
_extensions->glBindFragDataLocation( _glProgramHandle, itr->second, reinterpret_cast<const GLchar*>(itr->first.c_str()) );
}
// link the glProgram
GLint linked = GL_FALSE;
_extensions->glLinkProgram( _glProgramHandle );
_extensions->glGetProgramiv( _glProgramHandle, GL_LINK_STATUS, &linked );
_isLinked = (linked == GL_TRUE);
if( ! _isLinked )
{
OSG_WARN << "glLinkProgram \""<< _program->getName() << "\" FAILED" << std::endl;
@ -600,7 +661,7 @@ void Program::PerContextProgram::linkProgram(osg::State& state)
if( getInfoLog(infoLog) )
{
OSG_INFO << "Program \""<< _program->getName() << "\" "<<
"link succeded, infolog:\n" << infoLog << std::endl;
"link succeeded, infolog:\n" << infoLog << std::endl;
}
}
@ -747,6 +808,23 @@ bool Program::PerContextProgram::getInfoLog( std::string& infoLog ) const
return _extensions->getProgramInfoLog( _glProgramHandle, infoLog );
}
ProgramBinary* Program::PerContextProgram::compileProgramBinary(osg::State& state)
{
linkProgram(state);
GLint binaryLength = 0;
_extensions->glGetProgramiv( _glProgramHandle, GL_PROGRAM_BINARY_LENGTH, &binaryLength );
if (binaryLength)
{
ProgramBinary* programBinary = new ProgramBinary;
programBinary->allocate(binaryLength);
GLenum binaryFormat = 0;
_extensions->glGetProgramBinary( _glProgramHandle, binaryLength, 0, &binaryFormat, reinterpret_cast<GLvoid*>(programBinary->getData()) );
programBinary->setFormat(binaryFormat);
return programBinary;
}
return 0;
}
void Program::PerContextProgram::useProgram() const
{
_extensions->glUseProgram( _glProgramHandle );