OpenGL 1.5: Errors
Unhandled Exception!
Chapter.Errors.Intro
: Intro was not found.
Handling errors with C# is a very standard affair armed with try
and catch
. Unfortunately (again) for us these stalwarts are not quite up to the task of dealing with the beast that is OpenGL. In this brief detour we’ll look over some of the tactics we have available to handle those errors and some other tips and tricks.
Completing this chapter is optional and you can skip on to Chapter 2 and come back to this later.
Callbacks
void OnDebugMessage(
DebugSource source,
DebugType type,
uint id,
DebugSeverity severity,
int length,
nint pmessage,
nint userParam)
{
string message = Marshal.PtrToStringAnsi(pmessage, length);
Console.WriteLine("[{0} source={1} type={2} id={3}] {4}", severity, source, type, id, message);
}
GLDebugProc DebugMessageDelegate = OnDebugMessage;
GL.DebugMessageCallback(DebugMessageDelegate, nint.Zero);
GL.Enable(EnableCap.DebugOutput);
GL.Enable(EnableCap.DebugOutputSynchronous);
// setup code
What the heck is this and what are we expected to do with it?! This is a Delegate and when dealing with C based APIs they’re the tool of choice for handling errors. Lets start by looking at OnDebugMesssage
to see why it looks the way it does.
If you’re not familiar with C#’s Local Function feature it’s a little surprising at first. As the name suggests it allows a function to be declared within the local scope of another function. In our case we’re declaring a void
function with a laundry list of arguments.
We can check out the wiki to find GLAPI/glDebugMesssageCallback which documents exactly what we’re up to. Armed with this we can look through the arguments and get to know them a little better. DebugSource source
covers what part of OpenGL raised the message; DebugType type
is the class of message - typically DebugTypeError
; uint id
is a unique identifier for each message - very helpful when you have thousands pouring in; DebugSeverity severity
is the level of message from Notification
to High
to allow filtering. Finally we get to the less obvious ones: int length
and nint pmessage
are a pair and describe a C style string but thankfully C# can print these directly; then the mysteriousnint userParam
which is a pointer to a method supplied when the callback was registered - almost always null in our applications.
Phew that was a lot to cover I don’t blame you for taking a breather before we cover the rest of this code.
First we need to create a delegate to wrap our local function to prevent it from being garbage collected. Next is the most important line where we register this delgate with OpenGL. Next to our delegate is the input side of the nint userParam
pointer we can see in our callback. We’re passing nint.Zero
to indicate no function is registered.
We’ll look at why GL.Enable
exists and what it does later on - for now we’re going to use it to enable debug messages. Without enabling it our registered callback won’t get called!
Finally we also enable Synchronous debug output to force OpenGL to spit out errors as soon as they happen. This makes debugging a lot easier but comes with serious performance caveats. While learning and developing enabling this is a great idea but for production code you’ll want to consider your options.
Shaders Off
So far, hopefully, our shaders have compiled correctly first time but what happens if that’s not the case? Just like C# the GLSL compiler will happily point out all our flaws if we should only listen. Unlike the previous section this code doesn’t live in the main
function but in our Shader
class. Ask 10 programmers how to do this and you’ll get 12 options so here’s mine:
private void CompileShader(int shader)
{
GL.CompileShader(shader);
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out int code);
if (code != (int)All.True)
{
GL.GetShaderInfoLog(shader, out var infoLog);
throw new Exception($"Error compiling shader.{Environment.NewLine}{infoLog}");
}
}
Add this method to the Shader
class and replace GL.CompileShader
with just CompileShader
to hook it up. With the housekeeping done we can look at what’s been added. First up is the original call to GL.CompileShader
which continues to do an excellent job of compiling the shader.
Things get interesting with GL.GetShaderi
which is a format of call you’ll come to recognize. Whenever we ask for data back from OpenGL it will be done in this way. Here we’re asking for an integer
hence the i
suffix; can you figure out what float
or vector3
look like? As the shader either compiled sucessfully or didn’t why isn’t this GetShaderb
? Remember OpenGL is a C API and you might be surprised to find there’s no bool
type! A value of 0
is false and convention is 1
is true which you can double check by looking at the value of All.True
and All.False
.
If the shader failed to compile and All.False
was returned then we can do the same as before but this time with the aptly named GL.GetShaderInfoLog
. If you check the documentation the signature looks a little different. Here OpenTK does a little work for us and transforms the C string output into a C# string out
instead.
For our project we’ll throw an Exception
containing the info log then let the program terminate.
Somebody Call A RenderDoc!
We’ve looked at errors that happen before you can draw something to screen but these don’t offer any solace when your code runs but the resulting graphics are all wrong. While a good first approach is to output the interested part to the fragment colour channel it can only go so far.
RenderDoc is an open source graphics debugger that lets you break down every call that goes into a frame. Head on over and download the right version for you then we’ll briefly cover how to set it up for a typical .net project.
Before we dive in make sure you can build your project without debugging. You’ll need to know the full bin
directory of your project which typically ends with /LearnOpenTK/bin/Debug/net8.0/
depending on version and build type. Armed with this open up RenderDoc and take a moment to appreciate the functional design.

We need to launch our application through RenderDoc in order for it to do its magic. Start with the File
menu and Launch Application
or Ctrl+N
which doesn’t open any dialogs but changes the main tab to the Launch Application
tab. Inside this tab we’ll need to fill out the Executable Path
and Working Directory
fields before launching. The first, Executable Path, is the location of the LearnOpenTK.exe
file which might just be LearnOpenTK
depending on your OS. The second, Working Directory, should be the root folder of your project /code/LearnOpenTK
and NOT the net8.0
folder or anywhere else.
If everything is set correctly you should be able to launch your application via the Launch
button!

When attached RenderDoc will add debug text to the top left and let you know to hit F12
or PrintScreen
to generate a capture. Doing so will pause for a moment as it grabs everything then let you continue. We’re just rendering the same triangle every frame so hit that capture button and close your window.
Once RenderDoc has loaded your capture it’ll swap over to the Texture Viewer tab and by default show your Backbuffer Color output. On the left the Event Browser lists what happened during the frame, we can see our glClear
and glDrawArrays
along with some extra info. Our current project is so barebones there’s not really anything else we can learn with RenderDoc. When used with complex programs with convoluted draw operations it really shines.
I’ll stop here as explaining RenderDoc completely first requires you to have a solid understanding of OpenGL and we’re still working on that, and secondly because it’d double the size of this entire series! There’s a lot of resources out there on how to get the most of it so check them out if you’re interested.
Continue with Chapter 2: Chapter Still Buffering
Originally published on 2025/04/23