Managing OpenGL Resources

Started by
12 comments, last by Hodgman 10 years ago

If they're value types, it means they can be copied, and copied fairly easily by the user (as simply as a = b;). Copying a GPU resource can be quite an expensive operation, so I'd see value-references as quite a dangerous API. Also, there's no point in cloning some GPU resources -- e.g. in D3D a shader program is just code (no other state), so there's no need in ever having more than one instance of a particular program hanging around.

This was my intuition of the situation, where for the most part you want shallow copies and reference semantics. If deep copies are required then that should be offered explicitly and cannot just happen 'by mistake'.

One option that hasn't been discussed is to use non-copyable values. This avoids the issue of expensive copies by out-right preventing them.

You might still use shared_ptrs to share access to these objects - but there is a big difference between "an object that moves with reference semantics" and "an object passed by reference". By still being a value it allows you to also pass them by the cheaper plain reference or raw pointer. It also allows them to be stored more efficiently in contiguous arrays.

The precise way in which access to the object is passed around is a choice for the client code, not enforced by the handle type itself.

This makes a lot of sense especially if you only have one point of control, e.g. a resource manager/resource cache, which owns the 'real' resource. Client code can then request weak/non-owning references to these resources.

This is something I'll probably be playing around with pretty soon for meshes and materials. At the moment I have each object naively loading (and owning) its own textures and meshes, which while convenient is not particularly clever.

Advertisement

One option that hasn't been discussed is to use non-copyable values. This avoids the issue of expensive copies by out-right preventing them.

That's actually what I do for resources, but the value is only used inside the render-device implementation biggrin.png They're owned by an RAII value, and then references are given out to the user, e.g.


//hidden internally, not seen by the user
struct NativeBuffer : NonCopyable { /*GL, D3D, etc buffer handle member variable*/
  NativeBuffer( ... ) { /*acquire handle*/ }
  ~NativeBuffer() { /* release handle*/ }
};

//what the user gets:
typedef Pool<NativeBuffer>::Handle BufferHandle;

class Device
{
public:
  BufferHandle CreateBuffer(...);
  void         ReleaseBuffer( BufferHandle );
  ResourceLock MapBuffer( BufferHandle );
  void         UnmapBuffer( ResourceLock );
private:
  Pool<NativeBuffer> m_Buffers;
}

A system to allow shared ownership is implemented on top of this -- e.g. you might have a model-asset that owns some buffers, and then many model-instances that all share the one model-asset.

One option that hasn't been discussed is to use non-copyable values. This avoids the issue of expensive copies by out-right preventing them.

That's actually what I do for resources, but the value is only used inside the render-device implementation biggrin.png They're owned by an RAII value, and then references are given out to the user, e.g.


//hidden internally, not seen by the user
struct NativeBuffer : NonCopyable { /*GL, D3D, etc buffer handle member variable*/
  NativeBuffer( ... ) { /*acquire handle*/ }
  ~NativeBuffer() { /* release handle*/ }
};

//what the user gets:
typedef Pool<NativeBuffer>::Handle BufferHandle;

class Device
{
public:
  BufferHandle CreateBuffer(...);
  void         ReleaseBuffer( BufferHandle );
  ResourceLock MapBuffer( BufferHandle );
  void         UnmapBuffer( ResourceLock );
private:
  Pool<NativeBuffer> m_Buffers;
}

A system to allow shared ownership is implemented on top of this -- e.g. you might have a model-asset that owns some buffers, and then many model-instances that all share the one model-asset.

Once you give out references to their respective users what mechanism do you use to track usage in order to determine what should or shouldn't be cached? If that is even a relevant question for your framework.

Once you give out references to their respective users what mechanism do you use to track usage in order to determine what should or shouldn't be cached? If that is even a relevant question for your framework.

Creating/destroying a GL buffer is equivalent to calling malloc/free, except that you're dealing with GPU-addressable RAM. So, at this level I don't do any advanced management or caching.

Instead, the next level of the architecture can do those things. You have objects that are composed of GL resources, such as buffers -- e.g. a model asset loaded from disk. The model asset can have one-to-one "value" ownership over the buffers/resources. When the asset is created/destroyed, the resources are created/destroyed.

A model instance can then have shared ownership (e.g. reference counting / shared_ptr / etc) of a model asset. A file system can deal with reading bytes from disk, and a model factory can deal with converting those streams of bytes into model assets. An asset cache can perform the caching, e.g. by having a map/dictionary member that associates asset names with model asset objects. When the reference count on a model asset is zero, it can be deleted from the asset cache (which will free up the GL resources). When creating a model instance, it can use the asset cache, file system and model factory to either fetch an existing model asset or create a new one.

This topic is closed to new replies.

Advertisement