[osg-users] Removing objects with shared GL state from scene graph

Chris Djali krizdjali at gmail.com
Tue Apr 16 08:58:46 PDT 2019


Hi,

The following basically simulates the use case that's causing problems.


Code:
#include <random>

#include <osg/AutoTransform>
#include <osg/Group>
#include <osg/PositionAttitudeTransform>
#include <osg/ShapeDrawable>

#include <osgText/Text>

#include <osgViewer/Viewer>

/** A representation of something that gets edited.
 *  Pretend it's actually more complicated than this so that reference counting the number of attached text nodes is a nuisance. */
class World
{
public:
    World() : mScene(new osg::Group)
    {
        // add things so the viewer doesn't automatically zoom too far in to see the 'objects'
        
        auto worldCorners = { osg::Vec3(-11, -11, -11), osg::Vec3(-11, -11, 11),
                              osg::Vec3(-11, 11, -11), osg::Vec3(-11, 11, 11),
                              osg::Vec3(11, -11, -11), osg::Vec3(11, -11, 11),
                              osg::Vec3(11, 11, -11), osg::Vec3(11, 11, 11) };

        for (auto corner : worldCorners)
        {
            osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform();
            pat->setPosition(corner);
            pat->addChild(new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0, 0, 0), 0.1)));
            mScene->addChild(pat);
        }
    }

    osg::ref_ptr<osg::Group> getScene() { return mScene; }

    /** Adds an object with a label to the scene based on something the user has done. */
    void addObject(std::string name)
    {
        osg::ref_ptr<osg::PositionAttitudeTransform> object = new osg::PositionAttitudeTransform();
        object->setName(name);

        static std::random_device r;
        static std::default_random_engine randEngine(r());
        static std::uniform_real_distribution<> dist(-10, 10);

        object->setPosition(osg::Vec3(dist(randEngine), dist(randEngine), dist(randEngine)));

        osg::ref_ptr<osgText::Text> objectLabel = new osgText::Text();
        osg::ref_ptr<osg::AutoTransform> autoTransform = new osg::AutoTransform();
        autoTransform->addChild(objectLabel);
        autoTransform->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN);
        object->addChild(autoTransform);
        autoTransform->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
        objectLabel->setText(name);
        objectLabel->setCharacterSize(1);

        osg::ref_ptr<osg::Shape> shape = new osg::Sphere(osg::Vec3(0, 0, 0), 1);
        object->addChild(new osg::ShapeDrawable(shape));

        mScene->addChild(object);
    }

    /** Removes an object from the scene based on something the user has done. */
    void removeObject(std::string name)
    {
        osg::ref_ptr<osg::Node> child;
        for (unsigned int i = 0; i < mScene->getNumChildren(); ++i)
        {
            if (mScene->getChild(i)->getName() == name)
            {
                child = mScene->getChild(i);
                mScene->removeChild(i);
                break;
            }
        }

        // If we call child->releaseGLObjects() here, the text program will be released, too, even though it could still be being used by other labels.
        // If we don't, we may be detaching the last text node from the scene graph, and so the text program may never get released.
    }

private:
    osg::ref_ptr<osg::Group> mScene;
};


World world;
bool useNewViewer = true;

class EventHandler : public osgGA::GUIEventHandler
{
public:
    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        if (ea.getEventType() != osgGA::GUIEventAdapter::KEYDOWN)
            return false;

        // The user may wish to close the 3D view to work on something else and then reopen it later.
        // That would be a pain to implement directly, so instead, we simulate it by reopening the 3D view when it's closed if Return was pressed while it was open.
        if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Return)
        {
            useNewViewer = true;
            return true;
        }
        // Press a letter key without shift to add an object.
        else if (ea.getKey() >= 'a' && ea.getKey() <= 'z')
        {
            std::string name(1, ea.getKey());
            world.addObject(name);
            return true;
        }
        // Press a letter key with shift to remove an object.
        else if (ea.getKey() >= 'A' && ea.getKey() <= 'Z')
        {
            std::string name(1, ea.getKey() - ('Z' - 'z'));
            world.removeObject(name);
            return true;
        }

        return false;
    }
};

int main()
{
    osg::ref_ptr<osgViewer::Viewer> currentViewer;

    int returnCode = 0;

    while (useNewViewer)
    {
        currentViewer = new osgViewer::Viewer;
        currentViewer->setSceneData(world.getScene());
        currentViewer->addEventHandler(new EventHandler);

        useNewViewer = false;
        
        returnCode = currentViewer->run();
    }

    return returnCode;
}




Imagine this is part of a world editor for a game or CAD software or one of any number of types of software packages where you edit something and might want a 3D view of it, but wouldn't necessarily always want that 3D view taking up space in the window, so you'd possibly close it and then reopen it later. So I didn't have to make a load of extra stuff, this is simulated by opening a new viewer after the previous one closed if enter was pressed.

Pressing a letter key will add an object, and pressing the same letter with the shift key will remove it. Pretend that there are actually loads of different object types and not all of them have text labels so that keeping track of when the last text label is removed via reference counting or similar becomes a pain.

If you add at least one object, then remove all of them, then open a new view, you'll see OpenGL errors and text won't render correctly. This is because there were no osgText::Text nodes attached to the scene graph when the view was reset, so the program never got released, and the new text nodes try and reuse it with the new context, where it's not valid.

The obvious solution is to releaseGLObjects in the removeObject method, but if there are other objects still using text nodes, that's problematic as they'll still be using the program, and it would need recreating immediately.

Alternatively, removed objects could be kept around forever, attached to a hidden part of the scene graph so that anything that was ever used gets released when the context gets torn down. That means a bunch of junk needs keeping around forever, though.

Another option would just be to always have a hidden text node attached to the scene graph even when there are no objects. This solves the problem without much overhead but doesn't really seem like a tidy solution.

Thank you!

Cheers,
Chris

------------------
Read this topic online here:
http://forum.openscenegraph.org/viewtopic.php?p=75849#75849







More information about the osg-users mailing list