Jump to content

  • Log In with Google      Sign In   
  • Create Account


Challenges Abstracting OpenGL


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
2 replies to this topic

#1 CDProp   Members   -  Reputation: 891

Like
0Likes
Like

Posted 12 March 2014 - 07:23 PM

Greetings,

 

I am trying to write a C++ wrapper for OpenGL. I'm sure I'm reinventing the wheel a bit, but it's a learning exercise for me. My hope is to abstract things a bit to make it easier to spin up OpenGL programs, and to make the code easier to reason about a less error-prone. Following strict OO principles is somewhat of a secondary concern, but my #1 guiding principle here, I would say, is Item 18 from Effective C++: Make interfaces easy to use correctly and hard to use incorrectly.

 

Problem A: Binding Issues

 

With that said, one of the biggest challenges I'm running into is the fact that OpenGL is a state machine with a lot of global data. In particular, the bind points are causing me trouble. Take my Texture2D class, for example. If I were to mimic OpenGL's interface exactly, I would have methods like Bind, SubImage, etc. And that's pretty much what I've done. The problem is that there is that the user has to call Bind before they call SubImage, or else they'll get unexpected results (including, perhaps, subimaging a different texture entirely!). So it'd be nice to have a way to force the client to bind before calling SubImage.

 

Solution #1

 

My initial, insufficient solution was somewhat RAII-inspired. Here's how it worked:

 

  • First, I made the methods that required binding (SubImage, etc.) private.
  • Then, I wrote a Tex2DInterface class.
  • This class was a friend of Texture2D, and thus could access its private methods.
  • The constructor for Tex2DInterface accepts a Texture2D object and immediately called Bind() on it.
  • The destructor for Tex2DInterface calls Unbind() on the same object.
  • The Tex2DInterface class contains public pass-throughs for SubImage, etc.

So, if you needed to call SubImage on a Texture2D object, you were forced to create a Tex2DInterface object for it, and this object would automatically handle the binding and unbinding for you:

Texture2D myTex(...);

{
    Tex2DInterface ti(myTex);
    ti.SubImage(...);
} // Interface is destroyed, texture is unbound

The problem that almost immediately came to mind, though, is that one could create two interfaces for two different Texture2D objects in the same scope, and call methods on them in any order, and obviously this would be terrible. So, I quickly scratched this idea.

 

Solution #2 

 

I could just add a runtime check to see if the method in question (SubImage, or whatever) is being called from an object that is currently bound. This would leave it up to the client to make sure that the right object is bound. This solution won't prevent the client from making any mistakes, but it will catch mistakes and warn them so that they can fix them. The runtime checks can be compiled out of the release build.

 

Solution #3

 

For any methods that require binding, I could do the binding myself, inside the method. So, the SubImage method would call Bind() before doing it's thing. The upside to this is that the user of my Texture2D class can call any method on any Texture2D object without having to worry about calling Bind() first. In fact, the whole concept of binding could be hidden. The downside is that it would result in a lot of redundant calls to Bind(). Now, I'm already checking reduce redundant calls to glBindTexture, so that part won't be a problem, but the redundancy checks aren't free, either. The other downside is that if the client is totally oblivious to the binding going on under the hood, then they might be tempted to group their method calls in an inefficient way. My redundancy checks won't help them if they're calling a method on textureA, then textureB, and then textureA again, etc.

 

Problem B: Leaky Abstractions

 

Basically, my problem here is that I can create a nice Texture2D class that prevents the client from having to make direct OpenGL calls, but it doesn't prevent them from linking to OpenGL32.lib and making direct OpenGL calls. So, they could create a Texture2D object, and then mix it with direct OpenGL calls and mess everything up. I mean, this is a problem that even big OpenGL libraries like OpenSceneGraph have. I guess at some point, you just have to tell the client, "Don't do anything stupid." Is that the correct philosophy?



Sponsor:

#2 TheChubu   Crossbones+   -  Reputation: 3718

Like
1Likes
Like

Posted 12 March 2014 - 08:05 PM

I don't think abstracting the binding away is a good idea. 

 

The bind to edit OpenGL interface is just a plain reality, either you let the user do their own redundancy checks by exposing binding functions, or you abstract them away and face the consequences (and use DSA when possible).

 

My approach won't scale well probably when I start to expose more functionality but for now I do a little of both:

 

All OpenGL objects that can be bound implement the "IBindable" interface, which is public. And if something looks like a bind interface, it also implements it (ie, shader program object bind() method actually calls glUseProgram).

 

At the same time I provide both "safe" and "unsafe" operations, that means for example, having a GLBuffer object with an "upload(data)" method that does the binding/unbinding on its own, and an "uploadUnsafe(data)" method that just makes the glBufferData call and nothing else.

 

I decide what to call according to what I'm doing with the object at the time.

 

Each object holds its buffer Id and its binding target, so you can call "bind()" methods on them without any additional parameter.

 

I do the state tracking outside the GL objects, with a "StateTracker" class. If you want to check for redundant bindings, you bind objects through the state tracker (ie, stateTracker.bind(glObject) ) and it will figure out if its need to be bound or not, if you don't want any tracking you use the GL objects directly.

 

In this way there are no direct OpenGL anywhere except in the GLObject (and subclasses) implementations.

 

For me it works, and it accomplishes a simple objective, making the OpenGL calls easier to use rather than abstract them all away. And its not fool proof at all, you're using a low level API here. Even if its wrapped up in a nicer package, you kinda have to assume that the user knows what he is doing, otherwise you'd better off exposing a higher level abstraction that doesn't knows about OpenGL/D3D at all.


Edited by TheChubu, 12 March 2014 - 08:08 PM.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#3 CDProp   Members   -  Reputation: 891

Like
0Likes
Like

Posted 12 March 2014 - 08:41 PM

Even if its wrapped up in a nicer package, you kinda have to assume that the user knows what he is doing


Yeah, fair enough. This is only one layer of what will end up being a multilayered design, anyway. Thanks for all of your input.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS