Tuesday, June 5, 2012

OpenGL and Move Semantics

Every C++ programmer who regularly uses OpenGL quickly yearns to wrap an object-oriented facade over it. OpenGL's API is - how shall we put it - very state driven, unsafe and hard to manage. Adding C++ style automatic resource and object management would clearly be a good thing™.

 Safely in C++ typically means value semantics, that is, objects can by created freely, copied about, placed into containers and clean up automatically on scope exit. Now value semantics heavily implies copy semantics, But what happens when we are dealing with resources that don't like to be copied, and don't like to share? That just about describes every OpenGL resource!
 With c++98 modelling these types is problematic, we can declare the copy constructor and assignment operator private for safety, but now we are stuck with lifetime management and usability issues.

 The options are:

  1.  Give up and manage by pointer. This is the easiest method (at least initially). Safety guarantees are all but gone - we must explicitly clean up resources. Resource ownership issues abound. 
  2. Manage by a non-intrusive smart pointer, such as shared_ptr. This has the advantage of not requiring intrusive code to be written in the resource, and is safe. But, it imposes pointer semantics, implies sharing when in fact only unique access is permitted. 
  3. Manage by an internal ref counting. The object itself manages a ref count, either explicitly (such as COM AddRef) or implicitly by implementing in the copy constructor and assignment operator. This is intrusive, but at least gives the illusion of value semantics. Again, implies sharing.  
  4. Attempting to manage and limit object access solely through the use of a manager class. This is just too painfull.
In C++11 we can actually match the usage pattern of resources more closely to the underlying limitations. A GPU resource does not like to be copied, but it can be moved. It does not like to be shared, so don't share it!
Much has been written on how move semantics improves the performance on large classes like strings and vectors. But move semantics is also useful for safety on small handle classes (such as GPU resources)

 This leads to the following:
  1.  Resources should disable copying by deleting the copy construction and assignment operator.
        void operator=( const T& ) = delete;  
        T( const T& ) = delete; 
  2. A move constructor and move operator= should be written 
  3. The destructor should guard against freeing a moved resource. 
  4. Resources should typically be held by value or by unique_ptr. They should be passed to functions by reference. 
  5. Factories should return by value (if not polymorphic) or by unique_ptr. 
  6. If needed resources can always be moved (std::move) into a unique_ptr or shared_ptr.
        auto share = shared_ptr< T >( new T( std::move( value ) ) ); 
Now we have the best of both worlds, we can create, embed into containers, and can move things around as needed. In an emergency we can even move construct into a shared_ptr. We cannot copy or implicily share, which is exactly the prodding needed to do the right thing™.

Shader class skeleton:
class Shader: public boost::noncopyable {
 friend class ShaderManager;
public:
 Shader( GLuint program )
  : program_( program )
 { }
 ~Shader( ) {
            if ( program_ ) glDeleteProgram( program_ );
        }
 // move constructor
 Shader( Shader&& other )
  : program_( other.program_ )
 { 
  // wipe program_ so the destructor doesn't release it...
  other.program_ = 0; 
 }
 Shader& operator=( Program&& other ) {
     if ( program_ ) glDeleteProgram( program_ );
     program_ = other.program_;
      other.program_ = 0;
     return *this;
        }
private:
        GLuint program_;
};
Consider a scene class which holds some OpenGL shaders, The use of move semantics allows for a very natural usage pattern, safely.
 class Scene {
public:
   Scene( )
       :   explosion( ShaderFactory::Create( "Explosion" ) ) 
       ,   alien( ShaderFactory::Create( "Alien" ) ) 
    { }

   ~Scene( ){ 
     // nothing to do here cleanup is handled!
    }

    void onFrame( ) {
        explosion.bind( );  // yippee! value semantecs
        // stuff....
    }
private:
    Shader explosion;
    Shader alien;
};

No comments:

Post a Comment