Ejemplo n.º 1
0
osg::Program*
VirtualProgram::buildProgram(osg::State&        state, 
                             ShaderMap&         accumShaderMap,
                             AttribBindingList& accumAttribBindings,
                             AttribAliasMap&    accumAttribAliases)
{
    OE_TEST << LC << "Building new Program for VP " << getName() << std::endl;

    // build a new set of accumulated functions, to support the creation of main()
    refreshAccumulatedFunctions( state );

    // create new MAINs for this function stack.
    osg::Shader* vertMain = Registry::shaderFactory()->createVertexShaderMain( _accumulatedFunctions );
    osg::Shader* fragMain = Registry::shaderFactory()->createFragmentShaderMain( _accumulatedFunctions );

    // build a new "key vector" now that we've changed the shader map.
    // we call is a key vector because it uniquely identifies this shader program
    // based on its accumlated function set.
    ShaderVector keyVector;
    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
    {
        keyVector.push_back( i->second.first.get() );
    }

    // finally, add the mains (AFTER building the key vector .. we don't want or
    // need to mains in the key vector since they are completely derived from the
    // other elements of the key vector.)
    ShaderVector buildVector( keyVector );
    buildVector.push_back( vertMain );
    buildVector.push_back( fragMain );

    if ( s_dumpShaders )
        OE_NOTICE << LC << "---------PROGRAM: " << getName() << " ---------------\n" << std::endl;

    // Create the new program.
    osg::Program* program = new osg::Program();
    program->setName(getName());
    addShadersToProgram( buildVector, accumAttribBindings, accumAttribAliases, program );
    addTemplateDataToProgram( program );

    // finally, put own new program in the cache.
    _programCache[ keyVector ] = program;

    return program;
}
Ejemplo n.º 2
0
void
VirtualProgram::apply( osg::State& state ) const
{
    if (_shaderMap.empty() && !_inheritSet)
    {
        // If there's no data in the VP, and never has been, unload any existing program.
        // NOTE: OSG's State processor creates a "global default attribute" for each type.
        // Sine we have no way of knowing whether the user created the VP or OSG created it
        // as the default fallback, we use the "_inheritSet" flag to differeniate. This
        // prevents any shader leakage from a VP-enabled node.
        const unsigned int contextID = state.getContextID();
        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
        if( ! extensions->isGlslSupported() ) return;

        extensions->glUseProgram( 0 );
        state.setLastAppliedProgramObject(0);
        return;
    }

    // first, find and collect all the VirtualProgram attributes:
    ShaderMap         accumShaderMap;
    AttribBindingList accumAttribBindings;
    AttribAliasMap    accumAttribAliases;
    
    if ( _inherit )
    {
        const StateHack::AttributeVec* av = StateHack::GetAttributeVec( state, this );
        if ( av && av->size() > 0 )
        {
            // find the deepest VP that doesn't inherit:
            unsigned start = 0;
            for( start = (int)av->size()-1; start > 0; --start )
            {
                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[start].first );
                if ( vp && (vp->_mask & _mask) && vp->_inherit == false )
                    break;
            }
            
            // collect shaders from there to here:
            for( unsigned i=start; i<av->size(); ++i )
            {
                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
                if ( vp && (vp->_mask && _mask) )
                {
                    for( ShaderMap::const_iterator i = vp->_shaderMap.begin(); i != vp->_shaderMap.end(); ++i )
                    {
                        addToAccumulatedMap( accumShaderMap, i->first, i->second );
                    }

                    const AttribBindingList& abl = vp->getAttribBindingList();
                    accumAttribBindings.insert( abl.begin(), abl.end() );

                    const AttribAliasMap& aliases = vp->getAttribAliases();
                    accumAttribAliases.insert( aliases.begin(), aliases.end() );
                }
            }
        }
    }
    
    // next add the local shader components to the map, respecting the override values:
    for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
    {
        addToAccumulatedMap( accumShaderMap, i->first, i->second );
    }
    const AttribBindingList& abl = this->getAttribBindingList();
    accumAttribBindings.insert( abl.begin(), abl.end() );

    const AttribAliasMap& aliases = this->getAttribAliases();
    accumAttribAliases.insert( aliases.begin(), aliases.end() );


    if ( true ) //even with nothing in the map, we still want mains! -gw  //accumShaderMap.size() )
    {
        // next, assemble a list of the shaders in the map so we can use it as our
        // program cache key.
        // (Note: at present, the "cache key" does not include any information on the vertex
        // attribute bindings. Technically it should, but in practice this might not be an
        // issue; it is unlikely one would have two identical shader programs with different
        // bindings.)
        ShaderVector vec;
        vec.reserve( accumShaderMap.size() );
        for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
        {
            ShaderEntry& entry = i->second;
            vec.push_back( entry.first.get() );
        }
        
        // see if there's already a program associated with this list:
        osg::Program* program = 0L;
        
        // look up the program:
        {
            Threading::ScopedReadLock shared( _programCacheMutex );
            
            ProgramMap::const_iterator p = _programCache.find( vec );
            if ( p != _programCache.end() )
            {
                program = p->second.get();
            }
        }
        
        // if not found, lock and build it:
        if ( !program )
        {
            Threading::ScopedWriteLock exclusive( _programCacheMutex );
            
            // look again in case of contention:
            ProgramMap::const_iterator p = _programCache.find( vec );
            if ( p != _programCache.end() )
            {
                program = p->second.get();
            }
            else
            {
                VirtualProgram* nc = const_cast<VirtualProgram*>(this);
                program = nc->buildProgram( state, accumShaderMap, accumAttribBindings, accumAttribAliases);
            }
        }
        
        // finally, apply the program attribute.
        program->apply( state );
    }
}
Ejemplo n.º 3
0
void 
VirtualProgram::addShadersToProgram(const ShaderVector&      shaders, 
                                    const AttribBindingList& attribBindings,
                                    const AttribAliasMap&    attribAliases,
                                    osg::Program*            program )
{
#ifdef USE_ATTRIB_ALIASES
    // apply any vertex attribute aliases. But first, sort them from longest to shortest 
    // so we don't get any overlap and bad replacements.
    AttribAliasVector sortedAliases;
    sortedAliases.reserve( attribAliases.size() );
    sortedAliases.insert(sortedAliases.begin(), attribAliases.begin(), attribAliases.end());
    std::sort( sortedAliases.begin(), sortedAliases.end(), s_attribAliasSortFunc );

    for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
    {
        osg::Shader* shader = i->get();
        applyAttributeAliases( shader, sortedAliases );
    }
#endif

    // merge the shaders if necessary.
    if ( s_mergeShaders )
    {
        unsigned          vertVersion = 0;
        HeaderMap         vertHeaders;
        std::stringstream vertBody;

        unsigned          fragVersion = 0;
        HeaderMap         fragHeaders;
        std::stringstream fragBody;

        // parse the shaders, combining header lines and finding the highest version:
        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
        {
            osg::Shader* s = i->get();
            if ( s->getType() == osg::Shader::VERTEX )
            {
                parseShaderForMerging( s->getShaderSource(), vertVersion, vertHeaders, vertBody );
            }
            else if ( s->getType() == osg::Shader::FRAGMENT )
            {
                parseShaderForMerging( s->getShaderSource(), fragVersion, fragHeaders, fragBody );
            }
        }

        // write out the merged shader code:
        std::string vertBodyText;
        vertBodyText = vertBody.str();
        std::stringstream vertShaderBuf;
        if ( vertVersion > 0 )
            vertShaderBuf << "#version " << vertVersion << "\n";
        for( HeaderMap::const_iterator h = vertHeaders.begin(); h != vertHeaders.end(); ++h )
            vertShaderBuf << h->second << "\n";
        vertShaderBuf << vertBodyText << "\n";
        vertBodyText = vertShaderBuf.str();

        std::string fragBodyText;
        fragBodyText = fragBody.str();
        std::stringstream fragShaderBuf;
        if ( fragVersion > 0 )
            fragShaderBuf << "#version " << fragVersion << "\n";
        for( HeaderMap::const_iterator h = fragHeaders.begin(); h != fragHeaders.end(); ++h )
            fragShaderBuf << h->second << "\n";
        fragShaderBuf << fragBodyText << "\n";
        fragBodyText = fragShaderBuf.str();

        // add them to the program.
        program->addShader( new osg::Shader(osg::Shader::VERTEX, vertBodyText) );
        program->addShader( new osg::Shader(osg::Shader::FRAGMENT, fragBodyText) );

        if ( s_dumpShaders )
        {
            OE_NOTICE << LC 
                << "\nMERGED VERTEX SHADER: \n\n" << vertBodyText << "\n\n"
                << "MERGED FRAGMENT SHADER: \n\n" << fragBodyText << "\n" << std::endl;
        }
    }
    else
    {
        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
        {
            program->addShader( i->get() );
            if ( s_dumpShaders )
                OE_NOTICE << LC << "SHADER " << i->get()->getName() << ":\n" << i->get()->getShaderSource() << "\n" << std::endl;
        }
    }

    // add the attribute bindings
    for( VirtualProgram::AttribBindingList::const_iterator abl = attribBindings.begin(); abl != attribBindings.end(); ++abl )
    {
        program->addBindAttribLocation( abl->first, abl->second );
    }
}
Ejemplo n.º 4
0
osg::Program*
VirtualProgram::buildProgram(osg::State&        state, 
                             ShaderMap&         accumShaderMap,
                             AttribBindingList& accumAttribBindings)
{
    OE_TEST << LC << "Building new Program for VP " << getName() << std::endl;

    // build a new set of accumulated functions, to support the creation of main()
    refreshAccumulatedFunctions( state );

    // create new MAINs for this function stack.
    osg::Shader* vertMain = Registry::shaderFactory()->createVertexShaderMain( _accumulatedFunctions );
    osg::Shader* fragMain = Registry::shaderFactory()->createFragmentShaderMain( _accumulatedFunctions );

    // build a new "key vector" now that we've changed the shader map.
    // we call is a key vector because it uniquely identifies this shader program
    // based on its accumlated function set.
    ShaderVector keyVector;
    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
    {
        keyVector.push_back( i->second.first.get() );
    }

    // finally, add the mains (AFTER building the key vector .. we don't want or
    // need to mains in the key vector since they are completely derived from the
    // other elements of the key vector.)
    ShaderVector buildVector( keyVector );
    buildVector.push_back( vertMain );
    buildVector.push_back( fragMain );

    if ( s_dumpShaders )
        OE_NOTICE << LC << "---------PROGRAM: " << getName() << " ---------------\n" << std::endl;

    // Create the new program.
    osg::Program* program = new osg::Program();
    program->setName(getName());
    addShadersToProgram( buildVector, accumAttribBindings, program );
    addTemplateDataToProgram( program );


#if 0 // gw - obe, don't do this anymore

    // Since we replaced the "mains", we have to go through the cache and update all its
    // entries to point at the new mains instead of the old ones.
    if ( oldVertMain.valid() || oldFragMain.valid() )
    {
        ProgramMap newProgramCache;

        for( ProgramMap::iterator m = _programCache.begin(); m != _programCache.end(); ++m )
        {
            const ShaderVector& originalKey = m->first;

            osg::Program* newProgram = new osg::Program();
            newProgram->setName( m->second->getName() );

            ShaderVector newBuildKey( originalKey );
            newBuildKey.push_back( _vertMain.get() );
            newBuildKey.push_back( _fragMain.get() );
            addShadersToProgram( newBuildKey, m->second->getAttribBindingList(), newProgram );

            addTemplateDataToProgram( newProgram );

            newProgramCache[originalKey] = newProgram;
        }

        _programCache = newProgramCache;
    }
#endif

    // finally, put own new program in the cache.
    _programCache[ keyVector ] = program;

    return program;
}
Ejemplo n.º 5
0
void 
VirtualProgram::addShadersToProgram(const ShaderVector&      shaders, 
                                    const AttribBindingList& attribBindings,
                                    osg::Program*            program )
{
    if ( s_mergeShaders )
    {
        unsigned          vertVersion = 0;
        HeaderMap         vertHeaders;
        std::stringstream vertBody;

        unsigned          fragVersion = 0;
        HeaderMap         fragHeaders;
        std::stringstream fragBody;

        // parse the shaders, combining header lines and finding the highest version:
        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
        {
            osg::Shader* s = i->get();
            if ( s->getType() == osg::Shader::VERTEX )
            {
                parseShaderForMerging( s->getShaderSource(), vertVersion, vertHeaders, vertBody );
            }
            else if ( s->getType() == osg::Shader::FRAGMENT )
            {
                parseShaderForMerging( s->getShaderSource(), fragVersion, fragHeaders, fragBody );
            }
        }

        // write out the merged shader code:
        std::string vertBodyText;
        vertBodyText = vertBody.str();
        std::stringstream vertShaderBuf;
        if ( vertVersion > 0 )
            vertShaderBuf << "#version " << vertVersion << "\n";
        for( HeaderMap::const_iterator h = vertHeaders.begin(); h != vertHeaders.end(); ++h )
            vertShaderBuf << h->second << "\n";
        vertShaderBuf << vertBodyText << "\n";
        vertBodyText = vertShaderBuf.str();

        std::string fragBodyText;
        fragBodyText = fragBody.str();
        std::stringstream fragShaderBuf;
        if ( fragVersion > 0 )
            fragShaderBuf << "#version " << fragVersion << "\n";
        for( HeaderMap::const_iterator h = fragHeaders.begin(); h != fragHeaders.end(); ++h )
            fragShaderBuf << h->second << "\n";
        fragShaderBuf << fragBodyText << "\n";
        fragBodyText = fragShaderBuf.str();

        // add them to the program.
        program->addShader( new osg::Shader(osg::Shader::VERTEX, vertBodyText) );
        program->addShader( new osg::Shader(osg::Shader::FRAGMENT, fragBodyText) );

        if ( s_dumpShaders )
        {
            OE_NOTICE << LC 
                << "\nMERGED VERTEX SHADER: \n\n" << vertBodyText << "\n\n"
                << "MERGED FRAGMENT SHADER: \n\n" << fragBodyText << "\n" << std::endl;
        }
    }
    else
    {
        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
        {
            program->addShader( i->get() );
            if ( s_dumpShaders )
                OE_NOTICE << LC << "SHADER " << i->get()->getName() << ":\n" << i->get()->getShaderSource() << "\n" << std::endl;
        }
    }

    // add the attribute bindings
    for( VirtualProgram::AttribBindingList::const_iterator abl = attribBindings.begin(); abl != attribBindings.end(); ++abl )
    {
        program->addBindAttribLocation( abl->first, abl->second );
    }
}
Ejemplo n.º 6
0
void
VirtualProgram::apply( osg::State& state ) const
{
    if (_shaderMap.empty() && !_inheritSet)
    {
        // If there's no data in the VP, and never has been, unload any existing program.
        // NOTE: OSG's State processor creates a "global default attribute" for each type.
        // Sine we have no way of knowing whether the user created the VP or OSG created it
        // as the default fallback, we use the "_inheritSet" flag to differeniate. This
        // prevents any shader leakage from a VP-enabled node.
        const unsigned int contextID = state.getContextID();
        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
        if( ! extensions->isGlslSupported() ) return;

        extensions->glUseProgram( 0 );
        state.setLastAppliedProgramObject(0);
        return;
    }

    // first, find and collect all the VirtualProgram attributes:
    ShaderMap         accumShaderMap;
    AttribBindingList accumAttribBindings;
    AttribAliasMap    accumAttribAliases;
    
    // Build the active shader map up to this point:
    if ( _inherit )
    {
        accumulateShaders(state, _mask, accumShaderMap, accumAttribBindings, accumAttribAliases);
    }

    // next add the local shader components to the map, respecting the override values:
    {
        Threading::ScopedReadLock readonly(_dataModelMutex);

        for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
        {
            if ( i->second.accept(state) )
            {
                addToAccumulatedMap( accumShaderMap, i->first, i->second );
            }
        }

        const AttribBindingList& abl = this->getAttribBindingList();
        accumAttribBindings.insert( abl.begin(), abl.end() );

#ifdef USE_ATTRIB_ALIASES
        const AttribAliasMap& aliases = this->getAttribAliases();
        accumAttribAliases.insert( aliases.begin(), aliases.end() );
#endif
    }


    // next, assemble a list of the shaders in the map so we can use it as our
    // program cache key.
    // (Note: at present, the "cache key" does not include any information on the vertex
    // attribute bindings. Technically it should, but in practice this might not be an
    // issue; it is unlikely one would have two identical shader programs with different
    // bindings.)
    ShaderVector vec;
    vec.reserve( accumShaderMap.size() );
    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
    {
        ShaderEntry& entry = i->second;
        if ( i->second.accept(state) )
        {
            vec.push_back( entry._shader.get() );
        }
    }

    // see if there's already a program associated with this list:
    osg::ref_ptr<osg::Program> program;

    // look up the program:
    {
        Threading::ScopedReadLock shared( _programCacheMutex );

        ProgramMap::const_iterator p = _programCache.find( vec );
        if ( p != _programCache.end() )
        {
            program = p->second.get();
        }
    }

    // if not found, lock and build it:
    if ( !program.valid() )
    {
        // build a new set of accumulated functions, to support the creation of main()
        ShaderComp::FunctionLocationMap accumFunctions;
        accumulateFunctions( state, accumFunctions );

        // now double-check the program cache, and failing that, build the
        // new shader Program.
        {
            Threading::ScopedWriteLock exclusive( _programCacheMutex );

            // double-check: look again ito negate race conditions
            ProgramMap::const_iterator p = _programCache.find( vec );
            if ( p != _programCache.end() )
            {
                program = p->second.get();
            }
            else
            {
                ShaderVector keyVector;

                //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;

                program = buildProgram(
                    getName(),
                    state,
                    accumFunctions,
                    accumShaderMap, 
                    accumAttribBindings, 
                    accumAttribAliases, 
                    _template.get(),
                    keyVector);

                // global sharing.
                s_programRepo.share(program);

                // finally, put own new program in the cache.
                _programCache[ keyVector ] = program;
            }
        }
    }

    // finally, apply the program attribute.
    if ( program.valid() )
    {
        const unsigned int contextID = state.getContextID();
        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);

        osg::Program::PerContextProgram* pcp = program->getPCP( contextID );
        bool useProgram = state.getLastAppliedProgramObject() != pcp;

#ifdef DEBUG_APPLY_COUNTS
        {
            // debugging

            static int s_framenum = 0;
            static Threading::Mutex s_mutex;
            static std::map< const VirtualProgram*, std::pair<int,int> > s_counts;

            Threading::ScopedMutexLock lock(s_mutex);

            int framenum = state.getFrameStamp()->getFrameNumber();
            if ( framenum > s_framenum )
            {
                OE_NOTICE << LC << "Applies in last frame: " << std::endl;
                for(std::map<const VirtualProgram*,std::pair<int,int> >::iterator i = s_counts.begin(); i != s_counts.end(); ++i)
                {
                    std::pair<int,int>& counts = i->second;
                    OE_NOTICE << LC << "  " 
                        << i->first->getName() << " : " << counts.second << "/" << counts.first << std::endl;
                }
                s_framenum = framenum;
                s_counts.clear();
            }
            s_counts[this].first++;
            if ( useProgram )
                s_counts[this].second++;
        }
#endif

        if ( useProgram )
        {
            if( pcp->needsLink() )
                program->compileGLObjects( state );

            if( pcp->isLinked() )
            {
                if( osg::isNotifyEnabled(osg::INFO) )
                    pcp->validateProgram();

                pcp->useProgram();
                state.setLastAppliedProgramObject( pcp );
            }
            else
            {
                // program not usable, fallback to fixed function.
                extensions->glUseProgram( 0 );
                state.setLastAppliedProgramObject(0);
                OE_WARN << LC << "Program link failure!" << std::endl;
            }
        }

        //program->apply( state );

#if 0 // test code for detecting race conditions
        for(int i=0; i<10000; ++i) {
            state.setLastAppliedProgramObject(0L);
            program->apply( state );
        }
#endif
    }
}
Ejemplo n.º 7
0
void
VirtualProgram::apply( osg::State& state ) const
{
    if (_shaderMap.empty() && !_inheritSet)
    {
        // If there's no data in the VP, and never has been, unload any existing program.
        // NOTE: OSG's State processor creates a "global default attribute" for each type.
        // Sine we have no way of knowing whether the user created the VP or OSG created it
        // as the default fallback, we use the "_inheritSet" flag to differeniate. This
        // prevents any shader leakage from a VP-enabled node.
        const unsigned int contextID = state.getContextID();
        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
        if( ! extensions->isGlslSupported() ) return;

        extensions->glUseProgram( 0 );
        state.setLastAppliedProgramObject(0);
        return;
    }

    // first, find and collect all the VirtualProgram attributes:
    ShaderMap         accumShaderMap;
    AttribBindingList accumAttribBindings;
    AttribAliasMap    accumAttribAliases;
    
    if ( _inherit )
    {
        const StateHack::AttributeVec* av = StateHack::GetAttributeVec( state, this );
        if ( av && av->size() > 0 )
        {
            // find the deepest VP that doesn't inherit:
            unsigned start = 0;
            for( start = (int)av->size()-1; start > 0; --start )
            {
                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[start].first );
                if ( vp && (vp->_mask & _mask) && vp->_inherit == false )
                    break;
            }
            
            // collect shaders from there to here:
            for( unsigned i=start; i<av->size(); ++i )
            {
                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
                if ( vp && (vp->_mask && _mask) )
                {
                    ShaderMap vpShaderMap;
                    vp->getShaderMap( vpShaderMap );

                    for( ShaderMap::const_iterator i = vpShaderMap.begin(); i != vpShaderMap.end(); ++i )
                    {
                        addToAccumulatedMap( accumShaderMap, i->first, i->second );
                    }

                    const AttribBindingList& abl = vp->getAttribBindingList();
                    accumAttribBindings.insert( abl.begin(), abl.end() );

#ifdef USE_ATTRIB_ALIASES
                    const AttribAliasMap& aliases = vp->getAttribAliases();
                    accumAttribAliases.insert( aliases.begin(), aliases.end() );
#endif
                }
            }
        }
    }

    // next add the local shader components to the map, respecting the override values:
    {
        Threading::ScopedReadLock readonly(_dataModelMutex);

        for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
        {
            addToAccumulatedMap( accumShaderMap, i->first, i->second );
        }

        const AttribBindingList& abl = this->getAttribBindingList();
        accumAttribBindings.insert( abl.begin(), abl.end() );

#ifdef USE_ATTRIB_ALIASES
        const AttribAliasMap& aliases = this->getAttribAliases();
        accumAttribAliases.insert( aliases.begin(), aliases.end() );
#endif
    }


    if ( true ) //even with nothing in the map, we still want mains! -gw  //accumShaderMap.size() )
    {
        // next, assemble a list of the shaders in the map so we can use it as our
        // program cache key.
        // (Note: at present, the "cache key" does not include any information on the vertex
        // attribute bindings. Technically it should, but in practice this might not be an
        // issue; it is unlikely one would have two identical shader programs with different
        // bindings.)
        ShaderVector vec;
        vec.reserve( accumShaderMap.size() );
        for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
        {
            ShaderEntry& entry = i->second;
            vec.push_back( entry.first.get() );
        }
        
        // see if there's already a program associated with this list:
        osg::ref_ptr<osg::Program> program;
        
        // look up the program:
        {
            Threading::ScopedReadLock shared( _programCacheMutex );
            
            ProgramMap::const_iterator p = _programCache.find( vec );
            if ( p != _programCache.end() )
            {
                program = p->second.get();
            }
        }
        
        // if not found, lock and build it:
        if ( !program.valid() )
        {
            // build a new set of accumulated functions, to support the creation of main()
            ShaderComp::FunctionLocationMap accumFunctions;
            accumulateFunctions( state, accumFunctions );

            // now double-check the program cache, and failing that, build the
            // new shader Program.
            {
                Threading::ScopedWriteLock exclusive( _programCacheMutex );
                
                // double-check: look again ito negate race conditions
                ProgramMap::const_iterator p = _programCache.find( vec );
                if ( p != _programCache.end() )
                {
                    program = p->second.get();
                }
                else
                {
                    ShaderVector keyVector;

                    //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;

                    program = buildProgram(
                        getName(),
                        state,
                        accumFunctions,
                        accumShaderMap, 
                        accumAttribBindings, 
                        accumAttribAliases, 
                        _template.get(),
                        keyVector);

                    // finally, put own new program in the cache.
                    _programCache[ keyVector ] = program;
                }
            }
        }
        
        // finally, apply the program attribute.
        if ( program.valid() )
        {
            program->apply( state );

#if 0 // test code for detecting race conditions
            for(int i=0; i<10000; ++i) {
                state.setLastAppliedProgramObject(0L);
                program->apply( state );
            }
#endif
        }
    }
}