OpenGL: Why Is Your Code Producing a Black Window?

OpenGL: Why Is Your Code Producing a Black Window?

Introduction

One of the most common problems for novice, and sometimes experienced, OpenGL programmers is that they write new rendering code, run it, and it displays… nothing at all. Being more active on message boards again lately (Stack Overflow and OpenGL Discussion Boards), I noticed that this still covers a large part of the issues people ask about.

Diagnosing and solving these types of issues is often tricky because they can be caused by mistakes in almost any part of the OpenGL pipeline. This article explains some common reasons for the infamous “Black Window” problem, as well as approaches to track down these types of issues.

The following sections are mostly based on working with the Core Profile, which includes writing your own shaders. Much of it will apply to the fixed pipeline as well. If you want to make the transition to the Core Profile, check out my article about the topic.

Check for Errors

With hundreds of API calls, it can easily happen that a wrong argument is passed for one of them. Or less obviously, that API calls are made while not meeting all preconditions. You can check for these types of errors, and should do so routinely. The primary function for doing this is glGetError(). A useful approach is to have an error check that is always executed for debug builds at strategic places in your code. If you use assert macros, add a line like this for example at the end of rendering a frame:

ASSERT(glGetError() == GL_NO_ERROR);

If the assert triggers, temporarily add more of them across your code, gradually narrowing down which call causes the error based on the fact that the error was triggered between the last check that did not report an error, and the first one that did. Once you have isolated the error to a single call, reading the documentation for the call, and looking at the exact error code that was returned, should normally make it clear what went wrong.

Another important area of error checking is shader compilation and linking. At least for debug builds, check the shader compilation status after each shader compilation, using:

GLint status = 0; glGetShaderiv(shaderId, GL_COMPILE_STATUS, &status);

If status is GL_FALSE, you can retrieve the error messages using:

GLint logLen = 0; glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logLen); GLchar* logStr = new GLchar[logLen]; glGetShaderInfoLog(shaderId, logLen, 0, logStr); // report logStr delete[] logStr;

Checking success after linking the program looks similar, except that you use glGetProgramiv(), GL_LINK_STATUS,  and glGetProgramInfoLog().

When working with Frame Buffer Objects (FBO), there is another useful error check. After you finished setting up your FBO, check for success with:

ASSERT(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE));

Geometry Not in Field of View

This is by far one of the most common mistakes, and unfortunately not easy to track down. In this case, the whole rendering pipeline is correctly set up, but the the geometry is not in the coordinate range being mapped to the window.

The only good way to fix, or ideally avoid, this issue is a solid understanding of OpenGL coordinate systems and transformations. It is often best to start simple by placing the geometry around the origin, and placing the camera on the positive z-axis, pointing towards the origin. There is no need to use a projection transformation to get something to appear on the screen. Once this works, you can progress to setting up more advanced transformations, like perspective projections.

Common causes for the geometry not being in the field of view include:

  • The geometry has coordinates that are far away from the origin, while the camera points at the origin. In this case, you will either have to modify the coordinates of your geometry, apply a translation to move the geometry to the origin, or point your camera in the direction at the geometry.
  • The camera is inside the object, and backface culling is enabled. This can easily happen if no viewing transformation is set up at all. Make sure that you set up a viewing transformation.
  • The geometry is behind the camera. A variation of the same problem as the previous, but this typically happens if the viewing transformation is not set up correctly.
  • The clipping planes are set wrong. When using one of the common projection transformations, make sure that the range between the near and far clipping planes is consistent with the distance of your geometry from the camera. When not using a projection transformation, make sure that the z-coordinates after applying the viewing transformation are between -1.0 and 1.0.
  • Less common, but possible: The range of coordinates is much too small, so that in the extreme case, you end up drawing the entire geometry in a single pixel.

Black on Black

If there is a problem in the fragment part of the pipeline, it will often produce black pixels. When using shaders, a typical example is if the fragment shader uses texturing, but the texture was not properly set up. With the fixed pipeline, it can mean that no material color was set.

One very useful method to diagnose if this is happening is to set the clear color to something other than the default of black. I mostly set the clear color to something like yellow during development. If you do this, and see the outline of your geometry show up in black, you know that the problem is with the color of the fragments produced by your pipeline. This can be taken one step farther when using FBOs that are rendered to the primary framebuffer in the end. If you clear each render target with a different color, you can see where things break down.

Another useful approach can be applied when using relatively complex fragment shaders. To verify if there might be a problem with the output of the fragment shader, you can temporarily change it to simply produce a fixed color. If that color shows up, while you previously rendered all black, your problem is with the fragment shader.

 Vertex Data Not Properly Set Up

Current OpenGL requires vertex data to be in vertex buffers. If something goes wrong while setting up the data in those vertex buffers, it can result in no rendering at all. Verify that:

  • The vertex data itself that you store in the vertex buffers contains the correct coordinates for your geometry.
  • The correct vertex buffer is bound with glBindBuffer() when setting the vertex buffer data with a call like glBufferData().
  • All arguments to the vertex setup calls are correct. For example, make sure that the arguments to glVertexAttribPointer() match the format, sizes, etc. of your vertex data.

Faces Are Culled

By default, OpenGL expects the vertices of each face to be arranged in a counter-clockwise orientation. If you get this wrong, and have backface culling enabled, your faces can disappear. If you have any kind of suspicion that this might be happening, disable backface culling:

glDisable(GL_CULL_FACE);

Not Everything Is Bound and Enabled

There is a number of objects that need to be bound, and features that need to be enabled, for rendering to happen. If any of them are missing, you will often get no rendering at all.

There is no way around code inspection, or stepping through the code in a debugger, to make sure that everything is properly bound and enabled when the draw calls are executed. Items to look out for include:

  • The correct program is bound with glUseProgram().
  • Rendering goes to the primary framebuffer when intended, using glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0).
  • Vertex array object is bound with glBindVertexArray().
  • Vertex attributes are properly set up and enabled, using calls like glVertexAttribPointer() and glEnableVertexAttribArray().

Uniforms Were Not Set

Make sure that the uniforms used in your shaders are set to valid values before starting to draw. For example, if you miss to set a value for a uniform matrix used for transformations, it can result in your geometry not showing up.

Code inspection and debugging is the only reasonable way to find this. If you suspect that certain uniforms might not be set correctly, you can also try to temporarily simplify the shader to not use the value, and see if something changes.

Depth Buffer Is Not Cleared

If you use a depth buffer, and have depth testing enabled, make sure that you clear the depth buffer at the start of each frame.

A slight variation of this is that if you do not need a depth buffer for your rendering, make sure that you configure your context/surface without a depth buffer during initialization.

Frame Is Not Displayed

This problem is so trivial that it is almost embarrassing, but it does happen: If you use a double buffered visual (which you should in almost all cases), make sure that the buffers are swapped when you finish rendering the frame, so that the frame you rendered is actually displayed.

How exactly this is done is very system dependent. Some higher level frameworks handle this automatically after they invoked your rendering method. If that is not the case, look for a function that typically has SwapBuffers, or something similar as part of its name, and can be found in the window system interface, or the toolkit you use.

The symptom of this is that nothing at all is displayed, not even the clear color. Like in other cases, setting the clear color to something different from black or white helps recognizing that this might be your problem.

Context Is Not Properly Set Up, or Not Current

When nothing renders, it is possible that there was a problem while setting up the context, pixel format, rendering surface, etc. How this is done is highly platform dependent. Fortunately, it is at least easy to diagnose if the problem is in this area. Set your clear color to something other than black, using glClearColor(), and change your rendering method to only do a glClear(). If the window does not show your clear color, chances are that your context setup failed.