Help with macro for OpenGL error checking

OpenGL error checking is rather nasty and works like this in C:

glUseProgram(...); // Make a call
if (glGetError() != GL_NO_ERROR) {
    // error ocurred
}

Thus you have to call the glGetError function after every OpenGL call.

Now I'm using Rust and the gl-rs crate, and I have a handy glchk!() macro that does this error checking in debug builds and prints nice error messages.

Unfortunately, I'm fairly new to Rust, and I could use a code review and some advice.

E.g. this works well on statements:

glchk!(gl::UseProgram(...));

But any functions that return a value look a little weird. e.g.:

glchk!(uniform_mtx =
    gl::GetUniformLocation(program, CString::new("mtx").unwrap().as_ptr()));

If you had to implement this, how would you do so? The full macro is as follows:

macro_rules! glchk {
    ($s:stmt) => {
        $s;
        if cfg!(debug_assertions) {
            let err = gl::GetError();
            match err {
                gl::NO_ERROR => {
                },
               _ => {
                    let err_str = match err {
                        gl::INVALID_ENUM => "GL_INVALID_ENUM",
                        gl::INVALID_VALUE => "GL_INVALID_VALUE",
                        gl::INVALID_OPERATION => "GL_INVALID_OPERATION",
                        gl::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
                        gl::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
                        gl::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
                        gl::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
                        _ => "unknown error"
                    };
                    println!("{}:{} - {} caused {}",
                             file!(),
                             line!(),
                             stringify!($s),
                             err_str);
                }
            }
        }
    }
}

Not a direct answer, but your question contains the following code:

uniform_mtx =
    gl::GetUniformLocation(program, CString::new("mtx").unwrap().as_ptr());

This is a use-after-free / undefined behavior. The CString documentation uses exactly that code as an example of how not to use as_ptr().

1 Like

The nested and repeated match on err seems completely unnecessary to me. On top of that, since rightward drift is an enemy of readability, it's probably a good idea to replace the _ => match arm with the error arms contained within it.

In addition, using macro pattern repetitions there probably is a way to abstract the macro further to deal with any number of statements, not just 1.
The end result of that it cuts down on macro call noise, i.e. something like this:

glck!(foo;);
glck!(bar;);

Could be written as

glck!(
    foo;
    bar;
);
2 Likes

Very good catch! Thanks for pointing it out. I'll fix this.

Thanks for the tips. I ended up implementing it as you said.

The one issue with grouping multiple statements is that if an OpenGL call causes an error, the line!() macro returns the line where the glchk!() macro started, and not the line number of the actual statement of failure. Potentially related issue: https://github.com/rust-lang/rust/issues/39153

This is however not a showstopper and is only an inconvenience. My full macro now looks as follows:

macro_rules! glchk {
    ($($s:stmt;)*) => {
        $(
            $s;
            if cfg!(debug_assertions) {
                let err = gl::GetError();
                if err != gl::NO_ERROR {
                    let err_str = match err {
                        gl::INVALID_ENUM => "GL_INVALID_ENUM",
                        gl::INVALID_VALUE => "GL_INVALID_VALUE",
                        gl::INVALID_OPERATION => "GL_INVALID_OPERATION",
                        gl::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
                        gl::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
                        gl::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
                        gl::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
                        _ => "unknown error"
                    };
                    println!("{}:{} - {} caused {}",
                             file!(),
                             line!(),
                             stringify!($s),
                             err_str);
                }
            }
        )*
    }
}

It looks like it'll do the job nicely.
Unfortunately I don't think that the line!() macro reporting issue is easily solved, as technically there is no line number to refer to.

1 Like