13

The OpenGL standard pages states that the OpenGL is callable from C and C++. The API, however, is of course in pure C. As the OpenGL uses for example a lot of enumerations, using enum-classes (from C++11) could greatly reduce number of errors and make the API more feasible for beginners. It could be seen that lot of the bindings like OpenTK (for C#) are created; creating good C++ API shouldn't be much harder.

I weren't able to find anything that was more than an obscure wrapper, hence my questions:

  1. Is there a well-known C++ wrapper using C++11 facilities for OpenGL? and if not,
  2. Is something like this planned by anyone well-known (which especially means the Khronos)?
Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135

3 Answers3

11

The whole way OpenGL works does not really well map to OOP: http://www.opengl.org/wiki/Common_Mistakes#The_Object_Oriented_Language_Problem

What's not stated in this article is the context affinity problem. In OpenGL everything happens on the context, so a class "Texture", to be correct would be nothing more than a glorifed handle to be used with the context.

This is wrong:

class Texture {
/* ... */

public:
    void bind();
}

It would only work if the texture was part of the currently active context.

This is no better either:

class Texture {
/* ... */

public:
    void bind(Context &ctx);
}

The texture still must be part of the context ctx, and it would only work, if ctx was active at the moment.

So what about this:

class Context {
/* ... */
public:
    void bindTextureToUnit(TextureUnit &tu, Texture &t);
};

Better, but still not correct as the context must be the one currently active in the current thread. You may think "oh, I'll just throw an exception if context is not active". Please don't do this.

So what about this

class ActiveContext : Context {
/* ... */
public:
    void bindTextureToUnit(TextureUnit &tu, Texture &t);

}

Now you've ended up with making sure that there can be only one ActiveContext instance per thread. Which ends you up in all kinds of weird thread singleton mess.

In fact I numerously tried to implement a clean and sane mapping from OpenGL state and objects into a set of C++ classes, but there are always cases where is simply doesn't work out or ends up in horrible code mess.

IMHO it's far better to not try mapping the OpenGL API into a set of C++ classes (it can't be done sanely), but instead use the regular OpenGL API from specialized classes. Any OpenGL context management is so dependent in the program in questions, that it must be tailored specifically to said program.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Your objections seem somewhat artificial. In general you will have one window and one context (at least on Windows). It seems to me not that hard to isolate the window, context and the objects instantiated through the context from any other window and context. – Robinson Aug 23 '12 at 13:44
  • Well, I think that the Direct3D is a living example, that you *can* create graphics HAL in C++. – Bartek Banachewicz Aug 23 '12 at 13:50
  • 3
    @Robinson: You're thinking about games. There are other applications for OpenGL, that call for the use of multiple windows. And having multiple contexts, for example as a reference holder in shared configurations, is not uncommon. – datenwolf Aug 23 '12 at 14:01
  • 2
    @BartekBanachewicz: With the main difference, that Direct3D has been designed bottom up as a OOP API. OpenGL however is a context bound finite state machine, which is very different. – datenwolf Aug 23 '12 at 14:01
  • 2
    You seem to completely gloss over the option of leaving the user of the oo wrapper to manage context them selves. Either way, they have to do this. The wrapping classes just make the other aspects of GL a bit less painful. – thecoshman Jul 11 '13 at 17:49
6

Wrapping OpenGL and an OpenGL object model are two different concepts. OpenGL entities can easily be made into objects to wrap their functionality and indeed if you want to write a renderer that can be instantiated with, say, either OpenGL or D3D, this is a strict necessity.

I have classes like this:

    class Device
        class State
    class Buffer
            class BufferUniform
        class BufferVertices
        class BufferIndices
        class BufferArray
    class Texture
        class Texture1d
        class Texture2d
        class Texture3d
        class TextureCubeMap
        class TextureArray
        class TextureRender
        class TextureFrame          
    class Shader
        class ShaderPixel
        class ShaderVertex
        class ShaderGeometry
        class ShaderEvaluator
        class ShaderTessellator
        class ShaderProgram
        class ShaderGenerator
            class ShaderGeneratorParser
            class ShaderGeneratorNode
            class ShaderGeneratorCondition

... and either a D3D or an OpenGL version of each. Renderer<...> is instantiated with one set or the other at compile-time, depending on whether I want D3D or OpenGL to do the work.

Robinson
  • 9,666
  • 16
  • 71
  • 115
  • Also note DatenWolf's objections in terms of "correctness". But these problems are the same whether you use C or C++ - you still have to ensure you are using the correct context on the correct thread. It's really not that hard to ensure it all works OK though. – Robinson Aug 23 '12 at 13:40
  • My latest implementation has just 1 texture class (pass a flag to say "type") and on shader class (again, a simple enum flag for what the shader means), allowing me to cut down the number of entities I need considerably. – Robinson Feb 24 '16 at 10:32
2

Is there a well-known C++ wrapper using C++11 facilities for OpenGL? and if not,

No. There have been some attempts at it.

Is something like this planned by anyone well-known (which especially means the Khronos)?

The Khronos ARB doesn't really try to communicate directly with us about upcoming stuff. However, I highly doubt that they care about this sort of thing.

As for anyone else, again, there are some independent projects out there. But generally speaking, people who use OpenGL aren't that interested in doing this sort of thing.

The basic facts are these: OpenGL objects are directly associated with some global concept. Namely, the OpenGL context. Those objects can only be manipulated when the context they exist within (since objects can be shared between contexts) are active.

Therefore, any C++ object-oriented system must decide how fault-tolerant it wants to be. That is, what kinds of assurances that it wants to provide. If you call a function that operates on an object, how certain will you be that this call succeeds?

Here's a list of the levels of assurances that could reasonably be provided:

Completely Anal

In this case, you ensure that every function call either succeeds or properly detects an erroneous condition and fails properly.

In order to pull this off, you need an explicit OpenGL context object. Every OpenGL object must be associated with a share group among contexts (or a specific context itself, if an object type is not shareable).

All object member functions will have to take a context object, which must be the context to which they belong or a member of the share group for that context. There would have to be a per-thread cache of the context, so that they can check to see if the current context is the one they were given, and make it current if it is not.

When a context is destroyed, every object that relied on that context's existence must instantly become non-functional. Thus, every such object needs to have access to some tag (such as via a std::weak_ptr) to let them know that all of their function calls will fail.

If the objects are going to be properly RAII'd, then each object must be able to ensure that a context that they can be destroyed in (ie: glDelete* function) is current. And if it isn't, they need to make one current. So basically, every object needs to hold a reference to a valid context or otherwise needs to be able to create one.

On a personal note, I find this all to be painfully silly. No API is this fault tolerant, nor does it need to be. There's a lot of pointless babysitting and sharing of information. C++ is not a safe language, so it shouldn't waste good memory and/or performance just to provide you this level of safety.

Sane

Here, we get rid of the context-based safety checks. It is up to you the user to make sure that the proper contexts are current before trying to use any object in any way. This is just as true of C++. The main feature of this API is just being nicer than the raw C API.

The OpenGL objects would be RAII style, but they would also have a "dispose" function, which can be called if you want them to clear themselves without deleting their corresponding objects. This is useful if you're shutting down a context and don't want to have to run through and destruct all of the objects.

This system would basically assume that you're wanting pure Direct State Access for these classes. So none of the modifying member functions actually bind the object to the context. They can achieve that in one of several ways:

  1. Use EXT_DSA or the equivalent, where available.
  2. If EXT_DSA or equivalent is not available, store the modified state and send the modifications the next time this object is bound.
  3. Or just bind it, make the modification, and unbind it.

Certain kinds of modifications can't use #2. For example, glBufferData, glBufferSubData and glTexSubImage*D calls. The user really expects them to happen now. These functions should be named in such a way that they are distinguishable from guaranteed non-binding functions.

Any such binding functions should make no effort to restore the previous bound state of the object.

Permissive

Basically, there's a 1:1 correspondence between C++ member functions and C functions. Sure, you'll use C++ operator overloading and such to reduce the needless variations of functions. But when it comes right down to it, you're pretty much writing your C++ code the way you did your C code.

Objects may employ RAII, but they won't provide any real convenience beyond that. Member functions will either bind the object themselves or expect you to have bound them. Or they will use DSA and fail if DSA isn't available.

Why bother?

At the end of the day, there's really nothing much to gain from having a C++ interface to OpenGL. Sure, you get RAII. Well, you can get RAII just fine by using a std::unique_ptr with a special deleter functor (yes, really, this is very possible). But beyond some slight convenience with the API, what real expressive power do you gain that you did not have before?

If you're serious about using OpenGL to develop an application, you're probably going to build a rendering system that, relative to the rest of your code, abstracts OpenGL's concepts away. So nobody would see your fancy C++ interface besides your renderer. And your renderer could just as easily use OpenGL's C API. Why build your renderer off of an abstraction if it buys you pretty much nothing.

And if you're just toying around with OpenGL... what does it matter? Just use the interface you have.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 4
    You seem to have overlooked one detail. If one person/team wrote a nice clean C++ wrapper that was able to make using OpenGL in C++ easier to cope with but did not obstruct you from using anything in OpenGL (that you would normally be able to do) it can then be reused for ever more. There is no need to still be using a C api besides the fact that there is yet to be the one C++ api to rule them all. Sure we can all wrap these things ourselves, but let's move on. Let's have an OpenGL api that is easy to use, semantic and jells with modern decent C++. – thecoshman Aug 28 '13 at 10:48