r/opengl•
bwalk

OpenGL and OOP

I am currently refreshing my OpenGL knowledge and mixing it up with mordern C++. I have done what probably most of you guys would do and have created a bunch of class objects for shaders, programs, meshes, textures and so on. Each object hooks into the OpenGL API and calls appropriate functions to set state.

However, after reading a bit, I am not sure if this is the right approach. For example, in the FAQ! one problem is discussed. And after all, OpenGL is a big global state machine.

One alternative which I have found a couple of times is to implement a global management class for the GL context. Immediate advantages would be the ability to write a state stack and prevent unnecessary state switches easily. Disadvantages would be that this would be hard to get right in a multithreaded environment.

I know this probably falls under the chapter of premature optimizations but I am interested in how you guys manage your OpenGL contexts.

Posted Jan 5, 2015, 1:41 PM UTC

2119
-manabreak•

Personally, I have a Mesh class that contains vertices and indices, and is the only class that does the draw calls.

Renderer classes are, well, renderers that set the context in certain way. Each Renderer has begin(), render() and end() functions. In the first one, the renderer initializes the states as required (e.g. DeferredRenderer binds the G-buffer). In the second one, all the required meshes are drawn. In the last one, everything "leaky" is unbound (shaders and framebuffers, mainly). Each renderer is responsible for setting the GL states in the begin() function (i.e. enabling or disabling depth test and depth writes).

In the render() phase, each renderer can reference any number of RenderContentProvider classes. This is just an interface that each "content provider" can implement. These content providers do the actual drawing to the surrounding renderer context. Examples are 3D model rendering system, 2D sprite rendering system, text rendering system and so on.

So, in practice, the render loop goes like this:

for each Renderer in Renderers:
    Renderer.begin()
    for each RenderContentProvider in Renderer.ContentProviders:
        RenderContentProvider.process()
    Renderer.end()

After this "main" part, I call the quad renderer to output a quad in certain way (either output an intermediate texture like the depth buffer, or compose the final image), along with any full-screen post-processing effects (vignette, color effects, pixelization...).

And here's how my classes look like:

// Basic 2D texture. Keeps track of the texture ID
// as well as the texture properties
class Texture
    unsigned int m_width;
    unsigned int m_height;
    GLuint m_textureID;
    GLenum m_minFilter;
    GLenum m_magFilter;
    GLenum m_wrapS;
    GLenum m_wrapT;
    void bind(GLuint slot); // Binds the texture to the given slot

// Shader program. Wraps the GL shader program and
// caches the uniform locations.
class ShaderProgram
    GLuint m_programID;
    void bind(); // Binds the shader
    void setUniform(...); // Uniform setters

// Base class for all renderers. Usually renderers make use
// of the frame buffer (e.g. the DeferredRenderer)
class Renderer
    GLuint m_fboID;
    void begin();
    void render();
    void end();

// The deferred renderer.
class DeferredRenderer : public Renderer
    GBuffer m_gbuffer;

// The G-buffer
class GBuffer
    Texture m_color;
    Texture m_normal;
    Texture m_depth;

And so on. The main thing I've tried to keep in mind when designing the engine is to keep data separate from the functionality and making every GL-related portion responsible for their own state changes. I can't really help you with multi-threadedness, since my engine is single-threaded (although the program logic can be run in separate threads if needed).

7
bwalkOP•

Well, that's nice info. Especially the begin()/end() functions are a nice idea. Otherwise I guess I am not making much different to your approach. Thank you for the answer.

3
kraken_calamari•

this is essentially what I settled on as well.

1
vinnyvicious•

Could you explain a little more about your Renderers? I mean: why do you have multiple renderers? Do you have an OpenGL + D3D renderer?

Also, out of curiosity, how do you handle skinned/animated meshes?

1
-manabreak•

The multiple renderers I currently have are a deferred renderer (the main bulk of the scene), a forward renderer (special cases layered on top of the deferred scene), and a GUI system. At the moment there's only OpenGL support in the engine, but if I would add D3D support, I'd probably do an abstraction layer to the base Renderer class that would hide the OpenGL and D3D specific code.

The current iteration of the engine doesn't have support for animated meshes. However, I'd probably implement it as a component that's attached to the entities, and have separate render queues in the renderers for the animated meshes.

2
jringstad•

I don't personally encapsulate my drawing interactions into an OOP model, but I think if you want to go full OOP with it, having a state tracker is pretty much a necessity.

4
HomicidalChris•

One possibility would be to use a direct state access implementation inside your OOP classes.

https://www.opengl.org/registry/specs/ARB/direct_state_access.txt

If you need compatibility with ES/older OpenGL you could have a fallback path that implements glGet* queries at the beginning of each function and before returning sets the state back to what it was before.

This is similar to the approach I take in my projects; on modern GL I get no performance penalty for get and binds. On embedded projects I don't really have a problem with performance because I'm conscious of using too many objects/state changes anyway, and rather than implementing a global state tracker, I get the advantage of clean code that gives the illusion that function calls to my API have no impact on application state, resulting in both cleaner code and the ability to much more easily mix GL code from outside the API.

3
-manabreak•

Out of curiosity: why do you need glGet* functions? I have never run into a situation where I would need to do any glGet* calls during the render loop.

2
Cosmologicon•

There's one example in the link in the OP, where you want to avoid changing the GL state in a function that's behind an API:

void MyTexture::TexParameter(GLenum pname, GLint param) {
    GLuint boundTexture = 0;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*) &boundTexture);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexParameteri(GL_TEXTURE_2D, pname, param);
    glBindTexture(GL_TEXTURE_2D, boundTexture);
}
4
bwalkOP•

The question is, what is more expensive? A glGet* call or a redundant state change which probably could be optimized away by the driver.

5
Cosmologicon•

That's a question. I don't know if it's the question.

The point of using glGetIntegerv in this case is not to be more efficient. It's so that users of your API don't have to look at your internal code to know which states they need to change after every call.

5
H3g3m0n•

Sadly DSA doesn't account for the ability to have multiple contexts.

2
H3g3m0n•

I did the same thing. I have come to the conclusion that OOP and the OpenGL API level is the wrong way too go.

DSA helps relive the issue a bit design wise, but doesn't take into account having multiple OpenGL contexts.

And if you look at the Valve talk about porting the Source engine. They say DSA is fundamentally slower.

Yeh that might be 'premature optimization' but that is a guideline not a rule. Knowing when to break them is key to being a good programmer (for example GOTO is apparently used in video card drivers for optimization).

In this case optimizing a different way would require a totally different architecture.

The problem with OpenGL is if you have a mesh. It will have other properties that just about all other meshes will share. For example vertex attributes such as how positions, normals,textcoords and so on are organised out in memory. There's no need to respecify those properties every time.

A common approach for optimization is to group all your meshes together by material type. This means you only have to swap the vertex buffers and not respecify the vertex properties. You might also be able to swap textures without changing vertex properties if the materials use the same vertex attribute layout. Doing that with the OOP approach would loose that benefit as it would still be specifying that each time.

A better approach is to use higher level constructs. This might involve separating your rendering pipeline from OpenGL's rendering pipeline. For example make your own command list. Fire off all your objects to something, that can do all the sorting and minimize state changes. Internally deal with things as Materials not Shaders. You might still have some lowish level constructs like a shader manager but that would be used by the OpenGL pipeline.

Another approach might be a more complex object hierarchy. Where you work out the real dependencies of the OpenGL API.

gl_context1.Attributes("PosNormalTextcoord01").Program("Diffuse").Uniforms("UBO1").AddMesh(mesh1)
list_1 = gl_context1.Attributes("PosNormalTextcoord01").Program("Diffuse").Uniforms("UBO1").getCommandList()
// Or more likely: lists = gl_context1.getCommandListIterator()
gl_context1.Framebuffer("Main").ExecuteCommandList(list_1)

Basically abandon OpenGL's 'object model' as it doesn't map to an OOP model.

3
L-Spiro•

I am a senior graphics programmer at Square Enix.

#1: Avoid global state unless you have a very very specific and thought-out reason otherwise. Just based on your proposal, you seem not to have thought about shared contexts or multiple contexts. By making a “global management class” for the OpenGL context you are hard-limiting yourself to 1 context. How will you make tools? Qt has 1 context per control, for example.

Note that I enforce the use of only 1 context in my engine, but this can only work if you know exactly what you are doing and why, and even if you enforce 1 context and even if you can get it to work, it doesn’t justify using globals.

#2: The problems discussed in the FAQ are issues that should be raised, but should not be over-emphasized. There are many ways to handle both issues they discuss, including just by policy. For example, if my engine’s policy is just to have 1 context which begins at program start and ends at shut-down, the first issue can simply be ignored. The second issue they address regarding binding is slightly more involved to address, but a very simple version is to wrap the binding functions as well so that you always know what was last-bound. Never call binding functions directly—only use your wrapper function. Then you can always keep track locally what has been bound, and when your texture class is done creating your texture object you can simply rebind the previous texture.

This is also a great opportunity for RAII (even though the term itself is a bit misleading here). m_uiTexture = COpenGl::GenTexture(); if ( _ui32Total == 1 ) { COpenGl::CTempTexture2dBinder tt2bBinder( m_uiTexture ); if ( !GenerateMipMaps( _piImages, _ui32Total ) ) { return false; } } else { COpenGl::CTempTexture2dArrayBinder tt2abBinder( m_uiTexture ); if ( !GenerateMipMaps( _piImages, _ui32Total ) ) { return false; } }

tt2bBinder and tt2abBinder will bind the GLuint you give them on construction and unbind them (binding the previously bound texture) on destruction. If you plan on being able to load resources in background threads, you may want to enter and leave a critical section there (for example).

Also note that their examples are a bit contrived. A texture is a class for interfacing with the GPU. It should not know what a .BMP file is, or even what a file-system/hard-drive is. As suggested by my example, a separate class should be made for loading and manipulating images. This is the single-responsibility principle.

#3: It’s not premature optimization. Wrapping OpenGL “objects” and calls is absolutely necessary for performance. You should never set a state that is already set. This is absolutely terrible for performance, especially for major states such as shaders and textures. You should keep what is called a “shadow state” which keeps track of every OpenGL call you make (or almost anyway) and remembers the last value so that if you call the same function again with the same value it results in a no-op. Later you should use render-queues in order to maximize your no-ops. If you have 6 calls, 3 of which are “GL_TRUE” and 3 of which are “GL_FALSE”, they should be sorted so that they result in GL_TRUE GL_TRUE GL_TRUE GL_FALSE GL_FALSE GL_FALSE, not GL_TRUE GL_FALSE GL_TRUE GL_FALSE GL_TRUE GL_FALSE.

#4: Finally, you absolutely should be using object-oriented programming with OpenGL. Even if you just wrap the GLuint into a class at least you gain type-safety, but you can obviously do much more, such as ensure the lifetime of the object (guarantee no resource leaks) and take advantage of RAII in many ways (as already suggested). With proper abstraction, it also gives you the ability to support multiple platforms. For example, CTexture2d inherits from either COpenGlTexture2d, CDirectX9Texture2d, CDirectX11Texture2d, or etc.

L. Spiro

[EDIT] Lines starting with 4 spaces are treated like code eh? I’m waiting for my spaces to become lines of code… [/EDIT]

3
vinnyvicious•

Quick question: do you use GLM?

1
RowYourUpboat•

You could take a look at how existing open source object-oriented graphics engines do it. I know OGRE3D wraps both OpenGL and Direct3D in an object-oriented abstraction layer, although I'm not sure how up-to-date OGRE's OpenGL usage is. The Torchlight games use OGRE, btw.

1
vinnyvicious•
1
vinnyvicious•

This is a good discussion: http://gamedev.stackexchange.com/questions/25733/object-oriented-opengl

I wish we had more articles or books on the subject of modern game engine architecture.

1