Hello, and welcome to the pages of Greg’s Journal.

In this post, I would like to delve more in-depth on the topic of tools, which helps me to abstract OpenGL native calls.

OpenGL is a state machine controlled by the API provided by the driver. Most of this API is notoriously obscure and cumbersome. Silicon Graphics began developing the OpenGl in 1991 [1]. The predominant language of that time was C with its procedural approach. Since 2006, OpenGL has been managed by the non-profit technology consortium Khronos Group, but unfortunately, not a lot had changed in terms of overall API simplicity and conciseness.

To manage the complexity, I have created a small number of relatively versatile classes, which hides the actual API calls underneath.

In this chapter

Bindable GL resources

Since OpenGL is essentially a state machine, the state managed by a stack of “bindings.” The idea is similar to RAII principle [2] – I want the resource to be bound while it is needed and release immediately afterward.

Every “bindable” resource in my code is represented by the GlBindable interface:

common_gl/com.blaster.gl.GlAux::GlBindable

interface GlBindable {
    fun bind()
    fun unbind()
}

When we want to use the resource, we can bind it on spot:

common_gl/com.blaster.gl.GlAux::glBind

fun glBind(bindables: List<GlBindable>, action: () -> Unit) {
    bindables.forEach { it.bind() }
    action.invoke()
    bindables.reversed().forEach { it.unbind() }
}

The calls can be nested and combined:

common_gl/com.blaster.techniques.TextTechnique::draw

fun draw(call: () -> Unit) {
    glBind(listOf(program, font, rect)) {
        program.setTexture(GlUniform.UNIFORM_TEXTURE_DIFFUSE, font)
        call.invoke()
    }
}

It is not an efficient approach – we’re allocating memory on a JVM with each call, which can potentially lead to GC pauses, but if I care for the efficiency for specific cases – there is always a way to overcome this downfall. Do not forget about the root of all evil [3] when optimizing 🙂

Error handling for native GL calls

The OpenGL driver is a black box of magic sometimes, and I am trying to use every possibility to discover errors early on. Every native call in my code surrounded by the following:

common_gl/com.blaster.gl.GlAux::glCheck

fun <T> glCheck(action: () -> T): T {
    val result = action.invoke()
    checkForGLError()
    return result
}

Here is the example:

common_gl/com.blaster.gl.GlBuffer::bind

override fun bind() {
    glCheck { backend.glBindBuffer(target, handle) }
}

This code will execute the call and check for native errors immediately after. Again, if I will care for the performance, after the code is debugged and working, I can strip those checks out similarly to C/C++ assertions – they are removed in release variants of the program by compiler [4].

If error is detected, it will be recognized and propagated like any other Kotlin Error:

common_gl/com.blaster.gl.GlAux::GlError

class GlError(private val errorCode: Int) : Error() {
    override fun toString(): String {
        val msg = when (errorCode) {
            0x0   -> "GL_NO_ERROR"
            0x500 -> "GL_INVALID_ENUM"
            0x501 -> "GL_INVALID_VALUE"
            0x502 -> "GL_INVALID_OPERATION"
            0x503 -> "GL_STACK_OVERFLOW"
            0x504 -> "GL_STACK_UNDERFLOW"
            0x505 -> "GL_OUT_OF_MEMORY"
            else -> throw TODO("Unknown error code: $errorCode")
        }
        return "OpenGL error: $msg($errorCode)"
    }
}

I am converting OpenGL errors to Kotlin errors to have a possibility to react on them later on.

Controlling the global GL state

One more thing, which I want to mention is a helper class, which allows me to prepare and handle the common parameters of the state:

common_gl/com.blaster.gl.GlState

class GlState private constructor()

With this class, I can, for example, disable a backface culling for a certain part of a technique and I will never forget to switch it back on:

common_gl/com.blaster.gl.GlState::drawWithNoCulling

fun drawWithNoCulling(draw: () -> Unit) {
    disableCulling()
    draw.invoke()
    enableCulling()
}

desktop/com.blaster.impl.Skybox::drawSkybox

private fun drawSkybox() {
    GlState.drawWithNoCulling {
        skyboxTechnique.skybox(camera)
    }
}

The main idea is to avoid having any low-level native calls across the client code to abstract the GL subsystem.

I hope you found this reading enjoyable and will see you soon!

References


Leave a Reply

Your email address will not be published. Required fields are marked *