void DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent ) { // place a static bounding sphere on the graph since we intend to alter // the structure of the subgraph. const osg::BoundingSphere& bs = parent->getBound(); parent->setComputeBoundingSphereCallback( new StaticBound(bs) ); parent->dirtyBound(); ModelNodeMatrices models; // collect the matrices for all the MT's under the parent. Obviously this assumes // a particular scene graph structure. for( unsigned i=0; i < parent->getNumChildren(); ++i ) { // each MT in the group parents the same child. osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>( parent->getChild(i) ); if ( mt ) { // we have to deep-copy the primitives because we're going to convert them // to use draw-instancing. osg::Node* n = mt->getChild(0); models[n].push_back( mt->getMatrix() ); } } // get rid of the old matrix transforms. parent->removeChildren(0, parent->getNumChildren()); // maximum size of a slice. unsigned maxTexSize = POSTEX_MAX_TEXTURE_SIZE; unsigned maxSliceSize = (maxTexSize*maxTexSize)/4; // 4 vec4s per matrix. // For each model: for( ModelNodeMatrices::iterator i = models.begin(); i != models.end(); ++i ) { osg::Node* node = i->first.get(); std::vector<osg::Matrix>& matrices = i->second; // calculate the overall bounding box for the model: osg::ComputeBoundsVisitor cbv; node->accept( cbv ); const osg::BoundingBox& nodeBox = cbv.getBoundingBox(); osg::BoundingBox bbox; for( std::vector<osg::Matrix>::iterator m = matrices.begin(); m != matrices.end(); ++m ) { osg::Matrix& matrix = *m; bbox.expandBy(nodeBox.corner(0) * matrix); bbox.expandBy(nodeBox.corner(1) * matrix); bbox.expandBy(nodeBox.corner(2) * matrix); bbox.expandBy(nodeBox.corner(3) * matrix); bbox.expandBy(nodeBox.corner(4) * matrix); bbox.expandBy(nodeBox.corner(5) * matrix); bbox.expandBy(nodeBox.corner(6) * matrix); bbox.expandBy(nodeBox.corner(7) * matrix); } // calculate slice count and sizes: unsigned sliceSize = std::min(matrices.size(), (size_t)maxSliceSize); unsigned numSlices = matrices.size() / maxSliceSize; unsigned lastSliceSize = matrices.size() % maxSliceSize; if ( lastSliceSize == 0 ) lastSliceSize = sliceSize; else ++numSlices; // Convert the node's primitive sets to use "draw-instanced" rendering; at the // same time, assign our computed bounding box as the static bounds for all // geometries. (As DI's they cannot report bounds naturally.) ConvertToDrawInstanced cdi(sliceSize, bbox, true); node->accept( cdi ); // If the number of instances is not an exact multiple of the number of slices, // replicate the node so we can draw a difference instance count in the final group. osg::Node* lastNode = node; if ( numSlices > 1 && lastSliceSize < sliceSize ) { // clone, but only make copies of necessary things lastNode = osg::clone( node, osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_PRIMITIVES ); ConvertToDrawInstanced cdi(lastSliceSize, bbox, false); lastNode->accept( cdi ); } // Assign matrix vectors to the nodes, so the application can easily retrieve // the original position data if necessary. MatrixRefVector* nodeMats = new MatrixRefVector(); nodeMats->setName(TAG_MATRIX_VECTOR); nodeMats->reserve(lastNode != node ? sliceSize*(numSlices-1) : sliceSize*numSlices); node->getOrCreateUserDataContainer()->addUserObject(nodeMats); // ...and a separate one for lastNode if necessary MatrixRefVector* lastNodeMats = 0L; if (lastNode != node) { lastNodeMats = new MatrixRefVector(); lastNodeMats->setName(TAG_MATRIX_VECTOR); lastNodeMats->reserve(lastSliceSize); lastNode->getOrCreateUserDataContainer()->addUserObject(lastNodeMats); } // Next, break the rendering down into "slices". GLSL will only support a limited // amount of pre-instance uniform data, so we have to portion the graph out into // slices of no more than this chunk size. for( unsigned slice = 0; slice < numSlices; ++slice ) { unsigned offset = slice * sliceSize; unsigned currentSize = slice == numSlices-1 ? lastSliceSize : sliceSize; osg::Node* currentNode = slice == numSlices-1 ? lastNode : node; // this group is simply a container for the uniform: osg::Group* sliceGroup = new osg::Group(); // calculate the ideal texture size for this slice: osg::Vec2f texSize = calculateIdealTextureSize(currentSize, maxTexSize); OE_DEBUG << LC << "size = " << currentSize << ", tex = " << texSize.x() << ", " << texSize.y() << std::endl; // sampler that will hold the instance matrices: osg::Image* image = new osg::Image(); image->setName("osgearth.drawinstanced.postex"); image->allocateImage( (int)texSize.x(), (int)texSize.y(), 1, GL_RGBA, GL_FLOAT ); osg::Texture2D* postex = new osg::Texture2D( image ); postex->setInternalFormat( GL_RGBA16F_ARB ); postex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::NEAREST ); postex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::NEAREST ); postex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP ); postex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP ); postex->setUnRefImageDataAfterApply( true ); if ( !ImageUtils::isPowerOfTwo(image) ) postex->setResizeNonPowerOfTwoHint( false ); // Tell the SG to skip the positioning texture. ShaderGenerator::setIgnoreHint(postex, true); osg::StateSet* stateset = sliceGroup->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(POSTEX_TEXTURE_UNIT, postex, 1); stateset->getOrCreateUniform("oe_di_postex_size", osg::Uniform::FLOAT_VEC2)->set(texSize); // could use PixelWriter but we know the format. GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() ); for(unsigned m=0; m<currentSize; ++m) { const osg::Matrixf& mat = matrices[offset + m]; for(int col=0; col<4; ++col) for(int row=0; row<4; ++row) *ptr++ = mat(row,col); // store them int the metadata as well if (currentNode == node) nodeMats->push_back(mat); else lastNodeMats->push_back(mat); } // add the node as a child: sliceGroup->addChild( currentNode ); parent->addChild( sliceGroup ); } } }
bool DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent ) { if ( !Registry::capabilities().supportsDrawInstanced() ) return false; // place a static bounding sphere on the graph since we intend to alter // the structure of the subgraph. const osg::BoundingSphere& bs = parent->getBound(); parent->setInitialBound(bs); parent->dirtyBound(); ModelInstanceMap models; // collect the matrices for all the MT's under the parent. Obviously this assumes // a particular scene graph structure. for( unsigned i=0; i < parent->getNumChildren(); ++i ) { // each MT in the group parents the same child. osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>( parent->getChild(i) ); if ( mt ) { osg::Node* n = mt->getChild(0); //models[n].push_back( mt->getMatrix() ); ModelInstance instance; instance.matrix = mt->getMatrix(); // See whether the ObjectID is encoded in a uniform on the MT. osg::StateSet* stateSet = mt->getStateSet(); if ( stateSet ) { osg::Uniform* uniform = stateSet->getUniform( Registry::objectIndex()->getObjectIDUniformName() ); if ( uniform ) { uniform->get( (unsigned&)instance.objectID ); } } models[n].push_back( instance ); } } // get rid of the old matrix transforms. parent->removeChildren(0, parent->getNumChildren()); // This is the maximum size of the tbo int maxTBOSize = Registry::capabilities().getMaxTextureBufferSize(); // This is the total number of instances it can store // We will iterate below. If the number of instances is larger than the buffer can store // we make more tbos int maxTBOInstancesSize = maxTBOSize/4;// 4 vec4s per matrix. // For each model: for( ModelInstanceMap::iterator i = models.begin(); i != models.end(); ++i ) { osg::Node* node = i->first.get(); std::vector<ModelInstance>& instances = i->second; // calculate the overall bounding box for the model: osg::ComputeBoundsVisitor cbv; node->accept( cbv ); const osg::BoundingBox& nodeBox = cbv.getBoundingBox(); osg::BoundingBox bbox; for( std::vector<ModelInstance>::iterator m = instances.begin(); m != instances.end(); ++m ) { bbox.expandBy(nodeBox.corner(0) * m->matrix); bbox.expandBy(nodeBox.corner(1) * m->matrix); bbox.expandBy(nodeBox.corner(2) * m->matrix); bbox.expandBy(nodeBox.corner(3) * m->matrix); bbox.expandBy(nodeBox.corner(4) * m->matrix); bbox.expandBy(nodeBox.corner(5) * m->matrix); bbox.expandBy(nodeBox.corner(6) * m->matrix); bbox.expandBy(nodeBox.corner(7) * m->matrix); } unsigned tboSize = 0; unsigned numInstancesToStore = 0; if (instances.size()<maxTBOInstancesSize) { tboSize = nextPowerOf2(instances.size()); numInstancesToStore = instances.size(); } else { OE_WARN << "Number of Instances: " << instances.size() << " exceeds Number of instances TBO can store: " << maxTBOInstancesSize << std::endl; OE_WARN << "Storing maximum possible instances in TBO, and skipping the rest"<<std::endl; tboSize = maxTBOInstancesSize; numInstancesToStore = maxTBOInstancesSize; } // Convert the node's primitive sets to use "draw-instanced" rendering; at the // same time, assign our computed bounding box as the static bounds for all // geometries. (As DI's they cannot report bounds naturally.) ConvertToDrawInstanced cdi(numInstancesToStore, bbox, true); node->accept( cdi ); // Assign matrix vectors to the node, so the application can easily retrieve // the original position data if necessary. MatrixRefVector* nodeMats = new MatrixRefVector(); nodeMats->setName(TAG_MATRIX_VECTOR); nodeMats->reserve(numInstancesToStore); node->getOrCreateUserDataContainer()->addUserObject(nodeMats); // this group is simply a container for the uniform: osg::Group* instanceGroup = new osg::Group(); // sampler that will hold the instance matrices: osg::Image* image = new osg::Image(); image->setName("osgearth.drawinstanced.postex"); image->allocateImage( tboSize*4, 1, 1, GL_RGBA, GL_FLOAT ); // could use PixelWriter but we know the format. // Note: we are building a transposed matrix because it makes the decoding easier in the shader. GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() ); for(unsigned m=0; m<numInstancesToStore; ++m) { ModelInstance& i = instances[m]; const osg::Matrixf& mat = i.matrix; // copy the first 3 columns: for(int col=0; col<3; ++col) { for(int row=0; row<4; ++row) { *ptr++ = mat(row,col); } } // encode the ObjectID in the last column, which is always (0,0,0,1) // in a standard scale/rot/trans matrix. We will reinstate it in the // shader after extracting the object ID. *ptr++ = (float)((i.objectID ) & 0xff); *ptr++ = (float)((i.objectID >> 8) & 0xff); *ptr++ = (float)((i.objectID >> 16) & 0xff); *ptr++ = (float)((i.objectID >> 24) & 0xff); // store them int the metadata as well nodeMats->push_back(mat); } osg::TextureBuffer* posTBO = new osg::TextureBuffer; posTBO->setImage(image); posTBO->setInternalFormat( GL_RGBA32F_ARB ); posTBO->setUnRefImageDataAfterApply( true ); // so the TBO will serialize properly. image->setWriteHint(osg::Image::STORE_INLINE); // Tell the SG to skip the positioning texture. ShaderGenerator::setIgnoreHint(posTBO, true); osg::StateSet* stateset = instanceGroup->getOrCreateStateSet(); stateset->setTextureAttribute(POSTEX_TBO_UNIT, posTBO); // add the node as a child: instanceGroup->addChild( node ); parent->addChild( instanceGroup ); //OE_INFO << LC << "ConvertToDI: instances=" << numInstancesToStore << "\n"; } return true; }