Adventures in Shader-ville

g++ triangle.cpp ... -lglew32 -lopengl32
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x7): undefined reference to `__imp____glewDeleteProgram'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x27): undefined reference to `__imp____glewUseProgram'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x69): undefined reference to `__imp____glewEnableVertexAttribArray'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x90): undefined reference to `__imp____glewVertexAttribPointer'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0xc7): undefined reference to `__imp____glewDisableVertexAttribArray'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0xff): undefined reference to `__imp____glewCreateShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x11d): undefined reference to `__imp____glewShaderSource'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x131): undefined reference to `__imp____glewCompileShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x143): undefined reference to `__imp____glewGetShaderiv'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x18b): undefined reference to `__imp____glewCreateShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x1a9): undefined reference to `__imp____glewShaderSource'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x1bd): undefined reference to `__imp____glewCompileShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x1cf): undefined reference to `__imp____glewGetShaderiv'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x217): undefined reference to `__imp____glewCreateProgram'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x225): undefined reference to `__imp____glewAttachShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x23d): undefined reference to `__imp____glewAttachShader'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x255): undefined reference to `__imp____glewLinkProgram'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x26a): undefined reference to `__imp____glewGetProgramiv'
/tmp/cc2DLE4T.o:triangle.cpp:(.text+0x2ba): undefined reference to `__imp____glewGetAttribLocation'
collect2: ld returned 1 exit status

Does that look like anything to you? Yeah. Me neither. In honor of that fact, this article is intended to be understandable enough to be interesting and readable for even an inexperienced non-programmer type. However, it will also provide some very useful information which may save from grief someone else who is attempting the same thing as I just have. Therefore, I will try to provide as many useful links as possible.

Before we begin, I want to state that the solutions I came up with are only applicable if you are using OpenGL and MingW. Beyond that, I make no guarantees.

I’ve spent the past month or so (among other tasks) attempting to port the rendering code for Duet from a fixed-function pipeline over to a shader based system. The primary difference between the two is that shaders are programmable. The reasons that you would want to have a programmable graphics card are numerous. But mostly the functionality exists to facilitate different visual effects which would be inconvenient using fixed-function processing. In simple terms, “The fixed function pipeline is the legacy way, while using a shader program is the new way.” Most games these days make extensive use of shaders, and without them, fancy effects like motion blurs, masked specular reflection, or refraction under water would be impossible.

Erik and I have been interested in pursuing different adjustment effects in order to facilitate ease of world building and make the game prettier. For example, we might be using a particular tile, but want to tint it a bit, or desaturate it completely. These kinds of things can easily be done in Photoshop and a new asset simply imported into the game (although it feels a bit messy) so my initial gut reaction was that it was a bad idea to mess with it. After all, I knew that shaders were something I did not understand at all, and there would be a great deal of work and learning by failure involved. However, as time has gone on and we have continued longing for the flexibility that shaders could give us, I caved in and went about adding the functionality anyways. (Perhaps this was my first wrong move.)

The graphics engine for Duet runs on the Open Graphics Library (OpenGL for short) because it is easy to use, cross-platform capable, and why not? I started my journey to understanding shaders by looking into GLSL, (The OpenGL Shading Language) the programming language used by shaders under OpenGL. To my surprise, the language is very “C-like” and since the game is coded in C++ and I have been staring at “C-like” code for the past year and a half, I immediately understood what I was looking at. “So, that’s no big deal,” I told myself, confident that the tasty functionality that I desired was within my reach.

So I’ve got a game engine all coded up. The rendering functionality already works, so all I need to do is plug in some shaders and go…right? Well, not exactly. See, the issue is that unlike C++ code, GLSL code has to be compiled and linked on the graphics card itself. If your eyes glaze over when you read that, the simple explanation of compilation and linking of programs goes like this:

A person writes a program by typing in source code in a programming language like C++, which is designed to be human-readable. (believe it or not) After the programmer finishes writing something, in order to run the code, a program called a “compiler” must be used to translate the human-readable source code into a language that the computer can read, machine code. (1’s and 0’s, direct instructions to the CPU) When you write a program in a modern programming language, the source code is usually not all in one file, but in many. Each source code file is translated into machine code separately. The resulting files are called object files–one for each source code file. They each contain the machine-readable code for all of the contents of the original C++ file.

However, even after we have compiled the source code into object files, we have still not created an executable program; as these object files depend on each other to run properly. So they must be stitched perfectly together (or linked) into one big perfect sphere of a program. We call the program which does this stitching the “linker.” After the linker joins together the object files, it spits out an executable file that you can then run just like any other program. And then it crashes. (For a much more intelligible explanation of the process, along with an understanding of what makes Java so different from other programming languages, consult this lecture.)

Okay…so how do we compile and link on the graphics card? That’s right, we have to send the GLSL code, uncompiled and human readable to the graphics card. We can then tell the GPU to compile the shader code and link it, and finally we can run it.

The first and major obstacle to doing this (which nearly every tutorial out there fails to mention) is that the basic shader functions like glCreateShader() likely will not even be available under a core OpenGL context. (Particularly if you are coding on Windows, as it only implements OpenGL 1.1) If you try to use them, your program will not compile. The reasons behind this are a bit complicated, but briefly: any features not included in the OpenGL 1.1 standard are considered extensions on Windows. The extensions standard is great and all; it exists to maintain a minimal and lean core set of functionality for each version of OpenGL, and it allows for GPU manufactures to provide additional functionality to programmers without muddying up the water for everyone else, so to speak. But it causes us some serious headaches as Windows programmers because OpenGL depends upon the Operating System to define the support for those functions. On Windows, Microsoft has their own graphics API called Direct3D and they would be quite happy if everyone just used that and forgot about this whole OpenGL nonsense. Therefore, they routinely leave out definitions in their drivers for the features of the latest version of OpenGL, all while promoting the newest version of Direct3D as “better, faster, and stronger.” If a programmer wants to use a command that is outside of that specification, too bad.

Not exactly though, because luckily hardware manufacturers write their own drivers which support newer versions of the OpenGL spec. And any of the new functions can be accessed as long as you know what you are looking for. OpenGL can determine support through the graphics drivers, but you must specify the interface yourself. All newer features were just extensions at some point, so they can be accessed the same way. And if you want to use shaders on Windows XP, this is what you will have to do, define the functionality that you need based on some obscure and unreadable hardware handles that tie into the graphics driver. Luckily, there have been a few brave souls who have written handy libraries which can make the extra functionality much more easily available to you. These are called OpenGL extension loader libraries, or OpenGL Loading Libraries. There are a few available, but the best are probably GLee and GLew, I personally am using the latter. (Special Note: although GLee only claims to support up to OpenGL 3.0, it readily supports all the newest versions, because it is an automatically generated library.)

So, we download GLew, link with its lib file and #include it’s headers. We even write a custom loader to read in a text file with our shader in it (You could also try this one. There’s a million ways to load a file into your program. Just make sure you either give GL the length of the file in characters, or send it a NULL “/0” terminated file, I prefer the former, as it makes editing the source files a breeze.), so that we don’t have recompile the whole program every time we change a shader’s code. We compile and everything is peachy, except it’s NOT LINKING WHAT THE FUCK!? Yes, that is where all those errors at the top of the post came from. I could not for the life of me figure out what was going on. I told it to link with glew.lib and it should have found the definitions for all of those functions inside, but no dice.

Now, you may not have this problem when you are attempting to get this working. As it turns out, the linking issue was caused by a compatibility problem between “glew.lib” and the compiler which I am using, MinGW. (Minimalist Gnu for Windows) GLew seems to have been designed with the mindset that every programmer on earth uses Microsoft Visual Studio, therefore the distributions are mostly designed around that. The libraries included in the binary distribution have been built with Visual Studio and are not easily connected with MingW, in particular the dynamic library (.dll) version does NOT play nice at all. This leaves you with two options, you can try to rebuild the libraries yourself using MingW, which is somewhat difficult unless you have and IDE which can import .vcproj files, Visual Studio Project Files. (There is a makefile with the distribution as well, and you can use that, but you’ll need MSYS (in addition to MinGW) as well as a bit of gusto when it comes to using the command line interface.) After you build it, you can link as normal and cross your fingers. Or, you can simply link with the standard static version of the library “glews.lib.” If you need to know how to link with libraries for whatever reason, this tutorial is EXCELLENTE! (Even though the tutorial is for SDL, the same rules apply to all libraries you link with.)

Okay, hopefully now you’ve solved your compilation errors, your linker errors, and you have some shader loading code which is just BOMBING! <- If you want to check out that mod: here ya go.

(errors handsomely reproduced from here, although I promise you, this is close enough to what I was looking at)
Advertisements

Comments are closed.