Example #1
0
void
MPGeometry::renderPrimitiveSets(osg::State& state,
                                bool        renderColor,
                                bool        usingVBOs) const
{
    // check the map frame to see if it's up to date
    if ( _frame.needsSync() )
    {
        // this lock protects a MapFrame sync when we have multiple DRAW threads.
        Threading::ScopedMutexLock exclusive( _frameSyncMutex );

        if ( _frame.needsSync() && _frame.sync() ) // always double check
        {
            // This should only happen is the layer ordering changes;
            // If layers are added or removed, the Tile gets rebuilt and
            // the point is moot.
            std::vector<Layer> reordered;
            const ImageLayerVector& layers = _frame.imageLayers();
            reordered.reserve( layers.size() );
            for( ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
            {
                std::vector<Layer>::iterator j = std::find( _layers.begin(), _layers.end(), i->get()->getUID() );
                if ( j != _layers.end() )
                    reordered.push_back( *j );
            }
            _layers.swap( reordered );
        }
    }

    unsigned layersDrawn = 0;

    // access the GL extensions interface for the current GC:
    const osg::Program::PerContextProgram* pcp = 0L;

#if OSG_MIN_VERSION_REQUIRED(3,3,3)
	osg::ref_ptr<osg::GLExtensions> ext;
#else
    osg::ref_ptr<osg::GL2Extensions> ext;
#endif
    unsigned contextID;

    if (_supportsGLSL)
    {
        contextID = state.getContextID();
#if OSG_MIN_VERSION_REQUIRED(3,3,3)
		ext = osg::GLExtensions::Get(contextID, true);
#else
		ext = osg::GL2Extensions::Get( contextID, true );
#endif
        pcp = state.getLastAppliedProgramObject();
    }

    // cannot store these in the object since there could be multiple GCs (and multiple
    // PerContextPrograms) at large
    GLint tileKeyLocation       = -1;
    GLint birthTimeLocation     = -1;
    GLint opacityLocation       = -1;
    GLint uidLocation           = -1;
    GLint orderLocation         = -1;
    GLint texMatParentLocation  = -1;
    GLint minRangeLocation      = -1;
    GLint maxRangeLocation      = -1;

    // The PCP can change (especially in a VirtualProgram environment). So we do need to
    // requery the uni locations each time unfortunately. TODO: explore optimizations.
    if ( pcp )
    {
        tileKeyLocation      = pcp->getUniformLocation( _tileKeyUniformNameID );
        birthTimeLocation    = pcp->getUniformLocation( _birthTimeUniformNameID );
        opacityLocation      = pcp->getUniformLocation( _opacityUniformNameID );
        uidLocation          = pcp->getUniformLocation( _uidUniformNameID );
        orderLocation        = pcp->getUniformLocation( _orderUniformNameID );
        texMatParentLocation = pcp->getUniformLocation( _texMatParentUniformNameID );
        minRangeLocation = pcp->getUniformLocation( _minRangeUniformNameID );
        maxRangeLocation = pcp->getUniformLocation( _maxRangeUniformNameID );
    }
    
    // apply the tilekey uniform once.
    if ( tileKeyLocation >= 0 )
    {
        ext->glUniform4fv( tileKeyLocation, 1, _tileKeyValue.ptr() );
    }

    // set the "birth time" - i.e. the time this tile last entered the scene in the current GC.
    if ( birthTimeLocation >= 0 )
    {
        PerContextData& pcd = _pcd[contextID];
        if ( pcd.birthTime < 0.0f )
        {
            const osg::FrameStamp* stamp = state.getFrameStamp();
            if ( stamp )
            {
                pcd.birthTime = stamp->getReferenceTime();
            }
        }
        ext->glUniform1f( birthTimeLocation, pcd.birthTime );
    }

    // activate the tile coordinate set - same for all layers
    if ( renderColor )
    {
        state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );
    }

#ifndef OSG_GLES2_AVAILABLE
    if ( renderColor )
    {
        // emit a default terrain color since we're not binding a color array:
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    }
#endif

    // activate the elevation texture if there is one. Same for all layers.
    //if ( _elevTex.valid() )
    //{
    //    state.setActiveTextureUnit( 2 );
    //    state.setTexCoordPointer( 1, _tileCoords.get() ); // necessary?? since we do it above
    //    _elevTex->apply( state );
    //    // todo: probably need an elev texture matrix as well. -gw
    //}
    

    // track the active image unit.
    int activeImageUnit = -1;

    // remember whether we applied a parent texture.
    bool usedTexParent = false;

    if ( _layers.size() > 0 )
    {
        float prev_opacity        = -1.0f;

        // first bind any shared layers. We still have to do this even if we are
        // in !renderColor mode b/c these textures could be used by vertex shaders
        // to alter the geometry.
        int sharedLayers = 0;
        if ( pcp )
        {
            for(unsigned i=0; i<_layers.size(); ++i)
            {
                const Layer& layer = _layers[i];

                // a "shared" layer binds to a secondary texture unit so that other layers
                // can see it and use it.
                if ( layer._imageLayer->isShared() )
                {
                    ++sharedLayers;
                    int sharedUnit = layer._imageLayer->shareImageUnit().get();
                    {
                        state.setActiveTextureUnit( sharedUnit );

                        state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
                        // bind the texture for this layer to the active share unit.
                        layer._tex->apply( state );

                        // Shared layers need a texture matrix since the terrain engine doesn't
                        // provide a "current texture coordinate set" uniform (i.e. oe_layer_texc)
                        GLint texMatLocation = 0;
                        texMatLocation = pcp->getUniformLocation( layer._texMatUniformID );
                        if ( texMatLocation >= 0 )
                        {
                            ext->glUniformMatrix4fv( texMatLocation, 1, GL_FALSE, layer._texMat.ptr() );
                        }
                    }
                }
            }
        }
        if (renderColor)
        {
            // find the first opaque layer, top-down, and start there:
            unsigned first = 0;
            for(first = _layers.size()-1; first > 0; --first)
            {
                const Layer& layer = _layers[first];
                if (layer._opaque &&
                    //Color filters can modify the opacity
                    layer._imageLayer->getColorFilters().empty() &&
                    layer._imageLayer->getVisible() &&
                    layer._imageLayer->getOpacity() >= 1.0f)
                {
                    break;
                }
            }

            // interate over all the image layers
            for(unsigned i=first; i<_layers.size(); ++i)
            {
                const Layer& layer = _layers[i];

                if ( layer._imageLayer->getVisible() && layer._imageLayer->getOpacity() > 0.0f )
                {       
                    // activate the visible unit if necessary:
                    if ( activeImageUnit != _imageUnit )
                    {
                        state.setActiveTextureUnit( _imageUnit );
                        activeImageUnit = _imageUnit;
                    }

                    // bind the texture for this layer:
                    layer._tex->apply( state );

                    // in FFP mode, we need to enable the GL mode for texturing:
                    if ( !pcp ) //!_supportsGLSL)
                    {
                        state.applyMode(GL_TEXTURE_2D, true);
                    }

                    // if we're using a parent texture for blending, activate that now
                    if ( texMatParentLocation >= 0 && layer._texParent.valid() )
                    {
                        state.setActiveTextureUnit( _imageUnitParent );
                        activeImageUnit = _imageUnitParent;
                        layer._texParent->apply( state );
                        usedTexParent = true;
                    }

                    // bind the texture coordinates for this layer.
                    // TODO: can probably optimize this by sharing or using texture matrixes.
                    // State::setTexCoordPointer does some redundant work under the hood.
                    state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );

                    // apply uniform values:
                    if ( pcp )
                    {
                        // apply opacity:
                        if ( opacityLocation >= 0 )
                        {
                            float opacity = layer._imageLayer->getOpacity();
                            if ( opacity != prev_opacity )
                            {
                                ext->glUniform1f( opacityLocation, (GLfloat)opacity );
                                prev_opacity = opacity;
                            }
                        }

                        // assign the layer UID:
                        if ( uidLocation >= 0 )
                        {
                            ext->glUniform1i( uidLocation, (GLint)layer._layerID );
                        }

                        // assign the layer order:
                        if ( orderLocation >= 0 )
                        {
                            ext->glUniform1i( orderLocation, (GLint)layersDrawn );
                        }

                        // assign the parent texture matrix
                        if ( texMatParentLocation >= 0 && layer._texParent.valid() )
                        {
                            ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
                        }

                        // assign the min range
                        if ( minRangeLocation >= 0 )
                        {
                            ext->glUniform1f( minRangeLocation, layer._imageLayer->getImageLayerOptions().minVisibleRange().get() );
                        }

                        // assign the max range
                        if ( maxRangeLocation >= 0 )
                        {
                            ext->glUniform1f( maxRangeLocation, layer._imageLayer->getImageLayerOptions().maxVisibleRange().get() );
                        }
                    }

                    // draw the primitive sets.
                    for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
                    {
                        const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
                        if ( primitiveset )
                        {
                            primitiveset->draw(state, usingVBOs);
                        }
                        else
                        {
                            OE_WARN << LC << "Strange, MPGeometry had a 0L primset" << std::endl;
                        }
                    }

                    ++layersDrawn;
                }
            }
        }
    }

    // if we didn't draw anything, draw the raw tiles anyway with no texture.
    if ( layersDrawn == 0 )
    {
        if ( pcp )
        {
            if ( opacityLocation >= 0 )
                ext->glUniform1f( opacityLocation, (GLfloat)1.0f );
            if ( uidLocation >= 0 )
                ext->glUniform1i( uidLocation, (GLint)-1 );
            if ( orderLocation >= 0 )
                ext->glUniform1i( orderLocation, (GLint)0 );
        }

        // draw the primitives themselves.
        for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
        {
            const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
            primitiveset->draw(state, usingVBOs);
        }
    }

    else // at least one textured layer was drawn:
    {
        // prevent texture leakage
        // TODO: find a way to remove this to speed things up
        if ( renderColor )
        {
            glBindTexture( GL_TEXTURE_2D, 0 );

            // if a parent texture was applied, need to disable both.
            if ( usedTexParent )
            {
                state.setActiveTextureUnit(
                    activeImageUnit != _imageUnitParent ? _imageUnitParent :
                    _imageUnit );

                glBindTexture( GL_TEXTURE_2D, 0);
            }
        }
    }
}
Example #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;
    
    // 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
    }
}
Example #3
0
void
MPGeometry::renderPrimitiveSets(osg::State& state,
                                bool        usingVBOs) const
{
    // check the map frame to see if it's up to date
    if ( _frame.needsSync() )
    {
        // this lock protects a MapFrame sync when we have multiple DRAW threads.
        Threading::ScopedMutexLock exclusive( _frameSyncMutex );

        if ( _frame.needsSync() && _frame.sync() ) // always double check
        {
            // This should only happen is the layer ordering changes;
            // If layers are added or removed, the Tile gets rebuilt and
            // the point is moot.
            std::vector<Layer> reordered;
            const ImageLayerVector& layers = _frame.imageLayers();
            reordered.reserve( layers.size() );
            for( ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
            {
                std::vector<Layer>::iterator j = std::find( _layers.begin(), _layers.end(), i->get()->getUID() );
                if ( j != _layers.end() )
                    reordered.push_back( *j );
            }
            _layers.swap( reordered );
        }
    }

    unsigned layersDrawn = 0;


    osg::ref_ptr<osg::GL2Extensions> ext = osg::GL2Extensions::Get( state.getContextID(), true );
    const osg::Program::PerContextProgram* pcp = state.getLastAppliedProgramObject();

    GLint opacityLocation;
    GLint uidLocation;
    GLint orderLocation;
    GLint texMatParentLocation;

    // yes, it's possible that the PCP is not set up yet.
    // TODO: can we optimize this so we don't need to get uni locations every time?
    if ( pcp )
    {
        opacityLocation      = pcp->getUniformLocation( _opacityUniform->getNameID() );
        uidLocation          = pcp->getUniformLocation( _layerUIDUniform->getNameID() );
        orderLocation        = pcp->getUniformLocation( _layerOrderUniform->getNameID() );
        texMatParentLocation = pcp->getUniformLocation( _texMatParentUniform->getNameID() );
    }

    // activate the tile coordinate set - same for all layers
    state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );

    if ( _layers.size() > 0 )
    {
        float prev_opacity        = -1.0f;
        float prev_alphaThreshold = -1.0f;

        // first bind any shared layers
        // TODO: optimize by pre-storing shared indexes
        for(unsigned i=0; i<_layers.size(); ++i)
        {
            const Layer& layer = _layers[i];

            // a "shared" layer binds to a secondary texture unit so that other layers
            // can see it and use it.
            if ( layer._imageLayer->isShared() )
            {
                int sharedUnit = layer._imageLayer->shareImageUnit().get();
                {
                    state.setActiveTextureUnit( sharedUnit );
                    state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
                    // bind the texture for this layer to the active share unit.
                    layer._tex->apply( state );

                    // no texture LOD blending for shared layers for now. maybe later.
                }
            }
        }

        // track the active image unit.
        int activeImageUnit = -1;

        // interate over all the image layers
        for(unsigned i=0; i<_layers.size(); ++i)
        {
            const Layer& layer = _layers[i];

            if ( layer._imageLayer->getVisible() )
            {
                // activate the visible unit if necessary:
                if ( activeImageUnit != _imageUnit )
                {
                    state.setActiveTextureUnit( _imageUnit );
                    activeImageUnit = _imageUnit;
                }

                // bind the texture for this layer:
                layer._tex->apply( state );

                // if we're using a parent texture for blending, activate that now
                if ( layer._texParent.valid() )
                {
                    state.setActiveTextureUnit( _imageUnitParent );
                    activeImageUnit = _imageUnitParent;
                    layer._texParent->apply( state );
                }

                // bind the texture coordinates for this layer.
                // TODO: can probably optimize this by sharing or using texture matrixes.
                // State::setTexCoordPointer does some redundant work under the hood.
                state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );

                // apply uniform values:
                if ( pcp )
                {
                    // apply opacity:
                    float opacity = layer._imageLayer->getOpacity();
                    if ( opacity != prev_opacity )
                    {
                        _opacityUniform->set( opacity );
                        _opacityUniform->apply( ext, opacityLocation );
                        prev_opacity = opacity;
                    }

                    // assign the layer UID:
                    _layerUIDUniform->set( layer._layerID );
                    _layerUIDUniform->apply( ext, uidLocation );

                    // assign the layer order:
                    _layerOrderUniform->set( (int)layersDrawn );
                    _layerOrderUniform->apply( ext, orderLocation );

                    // assign the parent texture matrix
                    if ( layer._texParent.valid() )
                    {
                        _texMatParentUniform->set( layer._texMatParent );
                        _texMatParentUniform->apply( ext, texMatParentLocation );
                    }
                }

                // draw the primitive sets.
                for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
                {
                    const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
                    primitiveset->draw(state, usingVBOs);
                }

                ++layersDrawn;
            }
        }

        // prevent texture leakage
        glBindTexture( GL_TEXTURE_2D, 0 );
    }

    // if we didn't draw anything, draw the raw tiles anyway with no texture.
    if ( layersDrawn == 0 )
    {
        _opacityUniform->set( 1.0f );
        _opacityUniform->apply( ext, opacityLocation );

        _layerUIDUniform->set( (int)-1 ); // indicates a non-textured layer
        _layerUIDUniform->apply( ext, uidLocation );

        _layerOrderUniform->set( (int)0 );
        _layerOrderUniform->apply( ext, orderLocation );

        // draw the primitives themselves.
        for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
        {
            const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
            primitiveset->draw(state, usingVBOs);
        }
    }
}
Example #4
0
void
MPGeometry::renderPrimitiveSets(osg::State& state,
                                bool        usingVBOs) const
{
    // check the map frame to see if it's up to date
    if ( _frame.needsSync() )
    {
        // this lock protects a MapFrame sync when we have multiple DRAW threads.
        Threading::ScopedMutexLock exclusive( _frameSyncMutex );

        if ( _frame.needsSync() && _frame.sync() ) // always double check
        {
            // This should only happen is the layer ordering changes;
            // If layers are added or removed, the Tile gets rebuilt and
            // the point is moot.
            std::vector<Layer> reordered;
            const ImageLayerVector& layers = _frame.imageLayers();
            reordered.reserve( layers.size() );
            for( ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
            {
                std::vector<Layer>::iterator j = std::find( _layers.begin(), _layers.end(), i->get()->getUID() );
                if ( j != _layers.end() )
                    reordered.push_back( *j );
            }
            _layers.swap( reordered );
        }
    }

    unsigned layersDrawn = 0;


    // access the GL extensions interface for the current GC:
    osg::ref_ptr<osg::GL2Extensions> ext = osg::GL2Extensions::Get( state.getContextID(), true );
    const osg::Program::PerContextProgram* pcp = state.getLastAppliedProgramObject();

    // cannot store these in the object since there could be multiple GCs (and multiple
    // PerContextPrograms) at large
    GLint tileKeyLocation;
    GLint opacityLocation;
    GLint uidLocation;
    GLint orderLocation;
    GLint texMatParentLocation;

    // The PCP can change (especially in a VirtualProgram environment). So we do need to
    // requery the uni locations each time unfortunately. TODO: explore optimizations.
    if ( pcp )
    {
        tileKeyLocation      = pcp->getUniformLocation( _tileKeyUniformNameID );
        opacityLocation      = pcp->getUniformLocation( _opacityUniformNameID );
        uidLocation          = pcp->getUniformLocation( _uidUniformNameID );
        orderLocation        = pcp->getUniformLocation( _orderUniformNameID );
        texMatParentLocation = pcp->getUniformLocation( _texMatParentUniformNameID );
    }
    
    // apply the tilekey uniform once.
    ext->glUniform4fv( tileKeyLocation, 1, _tileKeyValue.ptr() );

    // activate the tile coordinate set - same for all layers
    state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );

    if ( _layers.size() > 0 )
    {
        float prev_opacity        = -1.0f;
        float prev_alphaThreshold = -1.0f;

        // first bind any shared layers
        // TODO: optimize by pre-storing shared indexes
        for(unsigned i=0; i<_layers.size(); ++i)
        {
            const Layer& layer = _layers[i];

            // a "shared" layer binds to a secondary texture unit so that other layers
            // can see it and use it.
            if ( layer._imageLayer->isShared() )
            {
                int sharedUnit = layer._imageLayer->shareImageUnit().get();
                {
                    state.setActiveTextureUnit( sharedUnit );
                    state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
                    // bind the texture for this layer to the active share unit.
                    layer._tex->apply( state );

                    // no texture LOD blending for shared layers for now. maybe later.
                }
            }
        }

        // track the active image unit.
        int activeImageUnit = -1;

        // interate over all the image layers
        //glDepthMask(GL_TRUE);
        for(unsigned i=0; i<_layers.size(); ++i)
        {
          //  if ( i > 0 )
            //    glDepthMask(GL_FALSE);

            const Layer& layer = _layers[i];

            if ( layer._imageLayer->getVisible() )
            {       
                // activate the visible unit if necessary:
                if ( activeImageUnit != _imageUnit )
                {
                    state.setActiveTextureUnit( _imageUnit );
                    activeImageUnit = _imageUnit;
                }

                // bind the texture for this layer:
                layer._tex->apply( state );

                // if we're using a parent texture for blending, activate that now
                if ( layer._texParent.valid() )
                {
                    state.setActiveTextureUnit( _imageUnitParent );
                    activeImageUnit = _imageUnitParent;
                    layer._texParent->apply( state );
                }

                // bind the texture coordinates for this layer.
                // TODO: can probably optimize this by sharing or using texture matrixes.
                // State::setTexCoordPointer does some redundant work under the hood.
                state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );

                // apply uniform values:
                if ( pcp )
                {
                    // apply opacity:
                    float opacity = layer._imageLayer->getOpacity();
                    if ( opacity != prev_opacity )
                    {
                        ext->glUniform1f( opacityLocation, (GLfloat)opacity );
                        prev_opacity = opacity;
                    }

                    // assign the layer UID:
                    ext->glUniform1i( uidLocation, (GLint)layer._layerID );

                    // assign the layer order:
                    ext->glUniform1i( orderLocation, (GLint)layersDrawn );

                    // assign the parent texture matrix
                    if ( layer._texParent.valid() )
                    {
                        ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
                    }
                }

                // draw the primitive sets.
                for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
                {
                    const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
                    primitiveset->draw(state, usingVBOs);
                }

                ++layersDrawn;
            }
        }

        // prevent texture leakage
        // TODO: find a way to remove this to speed things up
        glBindTexture( GL_TEXTURE_2D, 0 );
    }

    // if we didn't draw anything, draw the raw tiles anyway with no texture.
    if ( layersDrawn == 0 )
    {
        ext->glUniform1f( opacityLocation, (GLfloat)1.0f );
        ext->glUniform1i( uidLocation, (GLint)-1 );
        ext->glUniform1i( orderLocation, (GLint)0 );

        // draw the primitives themselves.
        for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
        {
            const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
            primitiveset->draw(state, usingVBOs);
        }
    }
}