Recently, the AmiCraftNova author was having issues with GLSL shaders, which got me thinking about how to debug them. Shaders need debugging, but standard debugging tools can't be used. So here are a few tips (for both OpenGL and Warp3D Nova).

Check the Compiler Logs

Both OpenGL and Warp3D Nova provide compilation logs. So check the logs when something fails. Obtain the OpenGL logs using glGetShaderInfoLog(), or glGetProgramInfoLog() after linking. For example:

// Compile it
glCompileShader(shader);
GLint compileSucceeded = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSucceeded);
if (!compileSucceeded) {
	// Compilation failed. Print error info
	fprintf(stderr, "Compilation of shader %s failed:\n", filename);
	GLint logLength = 0;
	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
	GLchar *errLog = (GLchar*)malloc(logLength);
	if (errLog) {
		glGetShaderInfoLog(shader, logLength, &logLength, errLog);
		fprintf(stderr, "%s\n", errLog);
		free(errLog);
	}
	else {
		fprintf(stderr, "Couldn't get shader log; out of memory\n");
	}

	glDeleteShader(shader);
	shader = 0;
}

GlGetProgramInfoLog() is used in a similar manner (see this tutorial).

Getting the log is simpler with Warp3D Nova because you can request it when you compile:

W3DN_Shader *shader = context->CompileShaderTags(&errCode, 
	W3DNTag_FileName, filename, W3DNTag_Log, &shaderLog, 
	W3DNTag_LogLevel, logLevel, TAG_DONE);

You can control the log detail with the W3DNTag_LogLevel tag. Log level W3DNLL_ERROR will only output errors, while W3DNLL_DEBUG will give you lots more information, including a disassembly of the compiled program.

IMPORTANT: Warp3D Nova logs should be disposed of with DestroyShaderLog() once you're done with it.

A Shortcut

Here's an easy way to get the compilation errors. First, compile the shader to SPIR-V format using GLSLangValidator, e.g.:

GLGLangValidator -G -o shader.vert.spv shader.vert

This will spit out any coding errors (e.g., syntax errors). If it compiled successfully, then use W3DNShaderInfo (it's included with Warp3D Nova) to get more information, e.g.:

W3DNShaderInfo shader.vert.spv

This will output any errors due to driver/hardware limitations. If it compiles successfully, then it'll output information about the shader's input, output and uniform variables. Otherwise you'll see compiler error messages.

HINT: Executing W3DNShaderInfo with the -v option will make it output a W3DNLL_DEBUG level log. This will also output the shader's disassembly.

What if it Compiles but Doesn't Work?

Okay, so it compiles successfully but you're not getting the results you want. What now? There's (currently) no way of setting breakpoints and stepping through the shader code with a debugger.

One of the first things I do is double-check that what I'm rendering is actually visible. You're not going to see anything if you accidentally put the object behind the camera! An easy way to double-check this is to change the fragment shader to one that outputs solid white (or some other colour). If you still see nothing, then either your vertex shader has a problem, or your main program (i.e.,the bit running on the CPU) has a bug. It helps to step back to simple code that you know works. From there, the problem can be isolated by a process of elimination.

Assuming you're seeing something on-screen but it's not what you expected, the next technique is to output one of the shader's internal variables to the screen so you can check that it's correct. For example, one test I did with the W3DNLogo demo was to output the vertex shader's calculated surface normal for each pixel. Interpreting the values can be tricky because you need to be able to map colours back to their original value. You'll get better at this the more you do it.

You may need to transform the variables to fit in the colour range. For example, surface normals are best transformed by:

vec3 outVal = 0.5 * normal + 0.5;

This scales and shifts the normal to fit into the range [0,1] for each colour channel. Then red means a normal pointing to the right, green is a normal pointing straight at the camera, etc.

Yes, it's tricky, but it's also effective. You track down where the problem lies by systematically checking the internal variables one at a time. Is it a logic/arithmetic error in the code? Or is the input data you're providing wrong (e.g., bad uniform data)? Once you've isolated the problem, then you can fix it.

I Think I May Have Found a Compiler Bug...

No matter how hard developers work on writing robust code, bugs are always a possibility. This is especially true of something as complex as a compiler.

If you think you've found a bug in the shader compiler, then please do the following:

  1. Double-check that you haven't made a mistake in your own code. If possible, test the shader on another operating system
  2. If you're still convinced that there's a compiler bug, then please file a bug report against OpenGL ES or Warp3D Nova (whichever you think is at fault). The bug tracker can be found at: http://amigadeveloper.com/bugreports/

NOTE: While you're welcome to look at the disassembled GPU code when confirming bugs, do realize that GPU disassembly can be hard to follow. Modern GPUs have a Single Instruction Multiple Thread (SIMT) architecture, and attempting to decypher its flow-control instructions can make your head spin.

Got Any Tips or Questions?

Got any useful tips of your own? Please post them in the comments section below. Questions and comments are also welcome.