/* OpenSceneGraph example, osgbindlesstex. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ ///\author David A Heitbrink /// This is an example implementation of the use of bindless textures. /// "bindless" textures are relatively simple concept, basically /// you get a texture handle, then ask the driver to keep said /// handle resident. /// /// Once the texture has been made resident, we need to upload /// the handle (a 64 bit unsigned int) to the shader. This can /// be done in a number of ways, through attributes, uniform /// buffer objects, shader buffer objects or just plain uniforms. /// /// The basic point of the bindless texture is to remove the need /// to bind a new texture every time we want to render something /// with a different texture. Generally speaking in broad terms /// driver overhead tends to be a huge bottle neck on modern /// hardware (as of late 2016). By using bindless textures /// we can remove alot of calls to the driver to switch active /// textures while rendering. What this also allows us to do /// is to consolidate more objects + draw states as we do /// not need to change textures, this save us alot of calls to /// the driver. /// /// This example combines instancing with bindless textures /// to draw 1000 cubes, each with a unique texture. This is /// a pretty simplified example, where each instance ID is /// used as a index into the array of textures. /// /// One of the powerfull things about bindless textures is it allows /// many more objects to be combined into a single drawable. /// However to do this you may need to add an attribute to /// use an index into the array of texture handles, and not /// just use the instance ID like in this example. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Hard coded constant for number unique textures const int TextureCount = 1000; // To use bindless textures, we need to tell the GPU to // enable the use of 64 bit integers and bindless textures // // // At this time (late 2016) NVidia drivers seem to dislike // the ARB, GL_ARB_bindless_texture, if you do not have a // NVidia driver, you will most likely have to change: // GL_NV_gpu_shader5 // to: // GL_ARB_gpu_shader5 //the XXX is so we can replace it with a string from TextureCount std::string vertShader= "#version 450 compatibility \n" "#extension GL_ARB_bindless_texture : require \n" "#extension GL_NV_gpu_shader5 : require // uint64_t \n" "//#extension GL_ARB_gpu_shader5 : require // uint64_t \n" "//#extension GL_ARB_gpu_shader_int64: require // uint64_t \n" "in float osg_FrameTime; \n" "out vec2 TexCoord; \n" "flat out int textureIndex; \n" "void main() { \n" " mat4 scale =mat4(0.01, 0.00, 0.00, 0.00, \n" " 0.00, 0.01, 0.00, 0.00, \n" " 0.00, 0.00, 0.01, 0.00, \n" " 0.00, 0.00, 0.00, 1.00); \n" " vec4 pos = gl_Vertex*scale; \n" " pos.x += ((gl_InstanceID%100)/10)*0.015-0.075; \n" " pos.y += (gl_InstanceID/100)*0.015 - 0.075; \n" " pos.z += (gl_InstanceID%10)*0.015 - 0.075; \n" " pos.w = 1; \n" " gl_Position = gl_ModelViewProjectionMatrix*pos; \n" " TexCoord = gl_MultiTexCoord0.xy; \n" " textureIndex = gl_InstanceID%XXX; \n" "} \n" ; //we could setup tex to be of type sampler2D, and not have to do //the type conversion, but I wanted to added code to test if tex //had a value set. //If we get a red cube, we are not getting our handle from our UBO, //if we get a black cube, then we are having an issue with the //texture handle itself std::string fragShader = "#version 450 compatibility \n" "#extension GL_ARB_bindless_texture : require \n" "#extension GL_NV_gpu_shader5 : require // uint64_t \n" "//#extension GL_ARB_gpu_shader5 : require // uint64_t \n" "//#extension GL_ARB_gpu_shader_int64: require // uint64_t \n" "uniform sampler2D TextureId; \n" "in vec2 TexCoord; \n" "flat in int textureIndex; \n" "layout (binding = 0, std140) uniform TEXTURE_BLOCK \n" "{ \n" " uint64_t tex[XXX]; \n" "}; \n" "void main() { \n" " int tIndex = (int)(textureIndex); \n" " sampler2D myText = sampler2D(tex[tIndex]); \n" " gl_FragColor = texture2D(myText,TexCoord); \n" " if (tex[tIndex] == 0) gl_FragColor.r = 1.0; \n" "} \n" ; ///This class provides a basic wraper for a Uniform Buffer Object ///or UBO, and provides the storage for the texture handles class BindlessBuffer: public osg::Referenced{ public: typedef osg::ref_ptr UniBufferObjRef; typedef osg::ref_ptr UniBufferBindingRef; typedef osg::ref_ptr HandleArrayRef; typedef osg::ref_ptr BindlessBufferRef; static BindlessBufferRef Make(size_t count){ BindlessBufferRef val = new BindlessBuffer(); val->_count = count; val->_sbbo = new osg::UniformBufferObject; val->_handles = new osg::UInt64Array(); val->_handles->resize(count*2,0); val->_handles->setBufferObject(val->_sbbo); val->_ssbb = new osg::UniformBufferBinding(0, val->_sbbo.get(), 0, sizeof(GLuint64)*count); return val; } BindlessBuffer& operator = (const BindlessBuffer& rhs){ if (this != &rhs){ _count=rhs._count; _sbbo =rhs._sbbo ; _ssbb =rhs._ssbb ; _handles = rhs._handles; } return *this; } BindlessBuffer(const BindlessBuffer& rhs):osg::Referenced(rhs){ if (this != &rhs){ _count=rhs._count; _sbbo =rhs._sbbo ; _ssbb =rhs._ssbb ; _handles = rhs._handles; } } UniBufferObjRef& Object(){return _sbbo;} UniBufferBindingRef& Binding(){return _ssbb;} HandleArrayRef& Handles(){return _handles;} int count(){return _count;} private: int _count; UniBufferObjRef _sbbo; UniBufferBindingRef _ssbb; HandleArrayRef _handles; BindlessBuffer():osg::Referenced(),_count(0){ } }; ///This class extends a Texture, when this is texture is applied ///the first time, it will setup all our texture handles, after that ///it will not make any more GL calls until it gets released class BindlessTexture: public osg::Texture2D { public: typedef osg::ref_ptr BufferRef; typedef std::vector > TextureList; typedef std::vector HandleList; typedef osg::ref_ptr< osg::Texture::TextureObject> TextureObjectRef; typedef std::vector TextureObjectList; typedef osg::buffered_object TextureObjectBuffer; BindlessTexture(); BindlessTexture(BufferRef, TextureList); BindlessTexture(const BindlessTexture& rhs, const osg::CopyOp& copy =osg::CopyOp::SHALLOW_COPY); void releaseGLObjects(osg::State* state) const; void resizeGLObjectBuffers(unsigned maxSize); void setBidlessIndex(unsigned int index); META_StateAttribute(osg, BindlessTexture, TEXTURE); void apply(osg::State& state) const; protected: void applyOnce(osg::State &state) const; mutable osg::buffered_object _handles; mutable TextureList _textureList; mutable osg::ref_ptr _buffer; mutable std::vector _isBound; mutable TextureObjectBuffer _textureBufferList; // array index = texture image unit. unsigned int _bindlessIndex; }; BindlessTexture::BindlessTexture():osg::Texture2D(),_bindlessIndex(0) { _isBound.resize(5,false); } BindlessTexture::BindlessTexture(const BindlessTexture& rhs, const osg::CopyOp& copy) :osg::Texture2D( rhs, copy ) { _isBound.resize(5,false); _buffer = rhs._buffer; _bindlessIndex = rhs._bindlessIndex; for(unsigned i=0; i image = _image; if (_handles[contextID].size() < _textureList.size()) _handles[contextID].resize( _textureList.size(),0); if (_textureBufferList[contextID].size() < _textureList.size()) _textureBufferList[contextID].resize( _textureList.size()); int txtcount = _textureList.size(); if (_buffer->count() < txtcount) txtcount = _buffer->count(); //for each actual texture we have, bind it, get the texture hande, assign the value to our UBO for (int i = 0; i bind(state); applyTexParameters(GL_TEXTURE_2D,state); applyTexImage2D_load(state,GL_TEXTURE_2D,image.get(), _textureWidth, _textureHeight, _numMipmapLevels); textureObject->setAllocated(true); _textureBufferList[contextID][i] = textureObject; //Here is where the "magic" happens, we get the texture handle for our texture, copy it to our UBO, //and then tell OpenGL to keep the handle resident _handles[contextID][i] = extensions->glGetTextureHandle( textureObject->id() ); std::vector &vec = _buffer->Handles()->asVector(); vec[i*2] = _handles[contextID][i]; _buffer->Object()->dirty(); _buffer->Handles()->dirty(); if ( _handles[contextID][i] != 0L || extensions->glIsTextureHandleResident( _handles[contextID][i]) == GL_FALSE) { extensions->glMakeTextureHandleResident( _handles[contextID][i] ); } } // update the modified tag to show that it is up to date. getModifiedCount(contextID) = image->getModifiedCount(); } void BindlessTexture::apply(osg::State& state) const { unsigned contextID = state.getContextID(); if ( _isBound[contextID] == false ) { applyOnce(state); _isBound[contextID] = true; }else{ //we should mostly hit this durring the lifetime of this object, //note we basically do nothing...... } } /// cleanup, we just need to tell OpenGL to release our texture handle void BindlessTexture::releaseGLObjects(osg::State* state) const { if ( state ) { unsigned contextID = state->getContextID(); osg::Texture2D::releaseGLObjects( state ); osg::GLExtensions* ext = osg::GLExtensions::Get( contextID, true ); for(unsigned i=0; i<_handles[contextID].size(); ++i) { ext->glMakeTextureHandleNonResident( _handles[contextID][i] ); _handles[contextID][i] = 0; } } } void BindlessTexture::resizeGLObjectBuffers(unsigned maxSize) { osg::Texture2D::resizeGLObjectBuffers( maxSize ); size_t handleSize = _handles.size(); size_t txtSize = _textureList.size(); size_t boundSize = _isBound.size(); if ( handleSize < maxSize ) { _isBound.resize(maxSize,false); } if ( handleSize < maxSize ) { _handles.resize( maxSize ); for(unsigned i=handleSize; i<_handles.size(); ++i){ for(unsigned j=0; j ImageRef; /////////////////////////////////////////////////////// ///Create an array of images, with checkerboard ///pattern with random color and size /// void createImageArray(osg::StateSet* attachPnt){ BindlessTexture::TextureList images; images.resize(TextureCount); BindlessBuffer::BindlessBufferRef buffer = BindlessBuffer::Make(TextureCount); srand (time(NULL)); for (int i =0; i < TextureCount; i++){ ImageRef tImage = new osg::Image(); int powerOf2 = rand()%6+4; const unsigned int imageSize = 1<allocateImage(imageSize,imageSize,1,GL_RGBA,GL_UNSIGNED_BYTE); unsigned char* buff = tImage->data(); const int stride = 4; unsigned char primaryColor[4]; int boxWidth = rand()%15+2; int boxLength = rand()%15+2; //light squares primaryColor[0] = rand()%128 + 128; primaryColor[1] = rand()%128 + 128; primaryColor[2] = rand()%128 + 128; //dark squares unsigned char secondaryColor[4]; secondaryColor[0] = rand()%128; secondaryColor[1] = rand()%128; secondaryColor[2] = rand()%128; for (int x = 0; x < imageSize; x++){ for (int y =0; y0; if (isPrimaryColor){ pixel[0] = primaryColor[0]; pixel[1] = primaryColor[1]; pixel[2] = primaryColor[2]; }else{ pixel[0] = secondaryColor[0]; pixel[1] = secondaryColor[1]; pixel[2] = secondaryColor[2]; } pixel[3] = 255; } } images[i] = tImage; std::stringstream sstr; sstr<<"Image"<setName(sstr.str()); } BindlessTexture* tex = new BindlessTexture(buffer,images); attachPnt->setTextureAttribute(0,tex,osg::StateAttribute::ON); attachPnt->setAttributeAndModes(buffer->Binding(), osg::StateAttribute::ON); } ///Create a cube centered at the origin, with given by size /// osg::Geometry* createCube(float scale, osg::Vec3 origin = osg::Vec3(0.0f,0.0f,0.0f) ) { osg::Geometry* geometry = new osg::Geometry; geometry->setName("TexturedCubeArray"); osg::Vec3Array* vertices = new osg::Vec3Array; geometry->setVertexArray(vertices); osg::Vec2Array* tcoords = new osg::Vec2Array(); geometry->setTexCoordArray(0,tcoords); origin -= osg::Vec3(scale/2.0f,scale/2.0f,scale/2.0f); osg::Vec3 dx(scale,0.0f,0.0f); osg::Vec3 dy(0.0f,scale,0.0f); osg::Vec3 dz(0.0f,0.0f,scale); { // front face vertices->push_back(origin); vertices->push_back(origin+dx); vertices->push_back(origin+dx+dz); vertices->push_back(origin+dz); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } { // back face vertices->push_back(origin+dy); vertices->push_back(origin+dy+dz); vertices->push_back(origin+dy+dx+dz); vertices->push_back(origin+dy+dx); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } { // left face vertices->push_back(origin+dy); vertices->push_back(origin); vertices->push_back(origin+dz); vertices->push_back(origin+dy+dz); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } { // right face vertices->push_back(origin+dx+dy); vertices->push_back(origin+dx+dy+dz); vertices->push_back(origin+dx+dz); vertices->push_back(origin+dx); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } { // top face vertices->push_back(origin+dz); vertices->push_back(origin+dz+dx); vertices->push_back(origin+dz+dx+dy); vertices->push_back(origin+dz+dy); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } { // bottom face vertices->push_back(origin); vertices->push_back(origin+dy); vertices->push_back(origin+dx+dy); vertices->push_back(origin+dx); tcoords->push_back(osg::Vec2(0.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,0.0f)); tcoords->push_back(osg::Vec2(1.0f,1.0f)); tcoords->push_back(osg::Vec2(0.0f,1.0f)); } osg::DrawArrays* primSet = new osg::DrawArrays(GL_QUADS, 0, vertices->size()); geometry->addPrimitiveSet(primSet); return geometry; } /// ///Here we are going to create our scene, basically we need to make sure our ///Bindless texture gets applied before our shader programs. /// /// osg::Group* CreateScene(){ osg::Group* sceneRoot= new osg::Group(); sceneRoot->setName("Root"); osg::Geode *geo = new osg::Geode(); geo->setName("Geo"); sceneRoot->addChild(geo); float size = 1.0f; osg::StateSet* scene_ss = sceneRoot->getOrCreateStateSet(); createImageArray(scene_ss); scene_ss->setMode(GL_DEPTH_TEST,osg::StateAttribute::ON); osg::ref_ptr geom = createCube(0.9f); osg::PrimitiveSet *prim = geom->getPrimitiveSet(0); //instanced elements must use VBOs geom->setUseDisplayList(false); geom->setUseVertexBufferObjects(true); geom->setCullingActive(false); prim->setNumInstances(1000); prim->dirty(); sceneRoot->addChild(geo); geo->addDrawable(geom.get()); osg::StateSet* ss = geo->getOrCreateStateSet(); std::string strTextureCount; std::stringstream ssconv; ssconv<>strTextureCount; std::string::size_type pos = vertShader.find("XXX"); vertShader.replace(pos,size_t(3),strTextureCount); pos = fragShader.find("XXX"); fragShader.replace(pos,size_t(3),strTextureCount); osg::Program* program = new osg::Program; osg::Shader* vertex_shader = new osg::Shader(osg::Shader::VERTEX, vertShader); program->addShader(vertex_shader); osg::Shader* fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragShader); program->addShader(fragment_shader); ss->setAttributeAndModes(program, osg::StateAttribute::ON); return sceneRoot; } int main(int argc, char** argv) { // set command line options osg::ArgumentParser arguments(&argc,argv); // construct the viewer. osgViewer::Viewer viewer(arguments); // add the stats handler viewer.addEventHandler(new osgViewer::StatsHandler); // add model to viewer. viewer.setSceneData( CreateScene() ); viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true); return viewer.run(); }