Brandon
2012-09-09 00:26:40
OpenGL Tutorial (Basics)
Contents:
[LEFT]
- Intro.
- Basics of Pointers/Typedefs.
- Layout of the OpenGL Plugin.
- OpenGL Basics.
- Function Information/Descriptions.
- Putting It Together.
- Hooking & Implementing Interceptions.
- Injecting vs. Wrapping.
- Simba & Lape Side.
- Writing An Include. Functions, Pointers, Etc..
Intro:
Please note that this tutorial is not for the faint of heart. Be warned that there is pointers involved; Both in Simba and in C++! A decent math background will be required and an extremely high one to develop even further than what is discussed (Vectors, Matrices, Quarternions, etc). Also note that this tutorial is not required to run GLHook or OpenGL but rather it is to develop OpenGL plugins.
[/LEFT]
[LEFT]Basics Of Pointers/Typedefs:[/LEFT]
The purpose of a "typedef" is to make life easier. It is so that we do not type the same thing over and over and also allows us to shorten code by a massive amount! It can be used in combination with functions so that you make things more readable when using function pointers.
One example is:
typedef void (WINAPI *ptr_glAccum) (GLenum op, GLfloat value);
The above is a declaration of a pointer to a function that returns void (nothing). Its parameters will be OP which is of type GLenum, and value which is of type GLfloat. An enum is short for enumerated value. In other words, it's an integral number. GLfloat is defined as a float (Extended in pascal script).
The function pointer is identified by its name: "ptr_glAccum" and it is named like that because it is a pointer to a function called glAccum. In reality, we can name it whatever we want but for conventional and reading purposes, I've chosen to name it as such.
All in all, the typedef is saying: Any function of this type(ptr_glAccum) will have two parameters, one of enum type and another that is a float. It will not return anything!
If you understood that, then you have understood the first step in the development of the OpenGL32.dll.
Now to use this declaration, we have to declare something of its type! I tend to call this "Pointer naming/declaring". Let's start with an example:
extern ptr_glAccum optr_glAccum;
For now ignore the "Extern" part of the declaration above. In the above, I create a function pointer called optr_glAccum. It has the same parameters and return values as ptr_glAccum. There is NO DIFFERENCE! The reason I do this is because I do not want to write out a whole new typedef again! This is how we use our previous typedef. The "o" infront of the "ptr" stands for original. When hooking things, you want to grab a pointer to the original function and store it for use later! This is essential!
Now to explain the extern part. Since in C++, we tend to split things into header and body files, it cannot be seen from another file. The extern will tell the compiler that this function is implemented later in some other file (body). Inother words, all our declarations are in headers (.h / .hpp files) and our implementations of those are in bodies (.cpp files).
That's it for this section.
Layout Of The OpenGL Plugin/Wrapper:
First we will create a header file with the extension .hpp. In it, we will have a header guard (Stops files from being included more than once) and our includes/declarations.
One header file looks like this (.hpp file):
#ifndef GLHOOK_HPP_INCLUDED
#define GLHOOK_HPP_INCLUDED
#include <gl/gl.h> //These are the same as {$I SRL/SRL.Simba} Include our needed stuff.
#include <gl/glu.h>
#include <gl/glext.h>
#include <windows.h>
#include <iostream>
#include "SharedMemory.hpp" //This is in quotes because we are including our own project file that we created! < bleh > means to include something in another folder.. usually compiler files.
#ifdef OPENGL32_EXPORTS //For our DLL to be exported/imported respectfully. We defined this in our setup!
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
#ifdef __cplusplus //If using a C++ compiler then define these so that names aren't mangled.
#define GL_EXTERN extern "C"
#define GL_EXPORT extern "C" DLL_EXPORT
#endif
/** VARIABLES: Start. **/
extern HMODULE OriginalGL; //A variable being declared but defined in a different file.
GL_EXTERN bool __stdcall Initialize(void); //Declarations of a function using the std calling convention.
GL_EXTERN bool __stdcall DeInitialize(void);
/** VARIABLES: End. **/
/** TYPEDEFS: Start (Some function pointers). **/
typedef void (WINAPI *ptr_glAccum) (GLenum op, GLfloat value);
typedef void (WINAPI *ptr_glAlphaFunc) (GLenum func, GLclampf ref);
typedef GLboolean (WINAPI *ptr_glAreTexturesResident) (GLsizei n, const GLuint *textures, GLboolean *residences);
typedef void (WINAPI *ptr_glArrayElement) (GLint index);
typedef void (WINAPI *ptr_glBegin) (GLenum mode);
typedef void (WINAPI *ptr_glBindBufferARB) (GLenum target, GLuint buffer);
The body for the above header would look like (.cpp file):
#include "GLHook.hpp" //We have to include our header file in the body or else none of the following would be declared.
/** VARIABLES: Start. **/
HMODULE OriginalGL = 0; //we just implemented our variable and gave it a value. Remember that the header does not do this. Headers declare. Bodies implement.
/** VARIABLES: End. **/
/** NAMING: Start. **/
ptr_glAccum optr_glAccum; //These are implementations. optr.. is of type ptr..
ptr_glAlphaFunc optr_glAlphaFunc;
ptr_glAreTexturesResident optr_glAreTexturesResident;
ptr_glArrayElement optr_glArrayElement;
ptr_glBegin optr_glBegin;
ptr_glBindBufferARB optr_glBindBufferARB;
This is the basics of the layout of the OpenGL plugin. All declarations MUST go in a header if they are to be seen by other files. Their respective implementations must go in a body file!
This is done for every OpenGL function that we need. Thus we do this for the 362 normal OpenGL functions and for the extended OpenGL functions.
Inother words, Microsoft/Windows only exports 362 OpenGL functions. If you export more, someone can check what extra you did and catch you in the action/possibly ban you. This does not mean that you cannot USE more than 362, it just means that you must only export the ones that are exported by default! All others can be used but DO NOT EXPORT them.
An exported function would have GL_EXPORT infront of it whereas extra functions would have GL_EXTERN infront of it. This is the reason we did all those #define OpenGL32_Exports, etc.. earlier!
OpenGL Basics:
This section will NOT break every single thing down to the very basic tutorial. It will only describe functions and things that are used and how/when to use them. It WILL give a background on the very basics but that is limited and up to you to learn.
First I'll start by apologizing for just posting OpenGL code without breaking it down piece by piece. I will attempt to do that right now..
Every OpenGL function/variable, etc starts with "GL" in lowercase.
Some examples are:
glAccum, glBindBuffer, glColor4ub.
They all begin with gl. The next thing that comes is a descriptor/name. For example, BindBuffer will do exactly what it says. Bind a buffer to a memory location. Color will enable all drawings to use the colour that was previously defined.. Etc. After the names, you may see type specifiers.
Type specifiers would be a suffix usually containing: u, b, f, fv, uv, ub, iv, i, etc and a number specifying the amount of parameters a function takes.
These specifiers allow users to know what type the function will return or accept for parameters. Example:
GLint, GLchar, GLfloat, GLdouble. //These are all types. No different from the ones without the GL. Infact they are defined as so: typedef GLint int; etc..
glColor4ub. //Takes unsigned bytes as its parameters.
glColor4iv. //Takes a void pointer to integers.
glColor4i. //Takes 4 integers. R, G, B, A.
glColor3i. //Takes 3 integers. R, G, B.
glColor3f. //Takes 3 floats. R, G, B.
Etc.. I for integer, f for float, v for void pointer, u for unsigned, b for byte.. And so on.
Next is different types of functions. ARB stands for Architecture Review Board. These functions weren't official back then and the OpenGL community refused to accept them as official. Thus they were appended with an ARB to specify that they are extensions!
The functions without ARB do the same thing as their ARB counter parts. Only difference is that it is now official OpenGL functions and are exported!
EXT functions are like ARB functions. They are also Extensions but there isn't much I can say here other than that.
NV, ATI, etc. NV are ONLY for Nvidia specific cards. As you can probably guess by now, ATI is for ATI/AMD/Radeon cards. Any function without these suffixes/prefixes are for all. Try to avoid these for portability reasons. Usually there is never a need for these.
wGL. wGL functions are usually specific to the Windows operating system but not limited to. They are called "Wiggle" functions. They usually emulate another function that GDI already defines. Example: SwapBuffers from GDI can be used instead of wglSwapBuffers. Same thing. Either way, don't mix GDI and OpenGL!
[LEFT]Function Descriptions & Usage:[/LEFT]
Note that I will NOT go through EVERY SINGLE OpenGL function. If you need a specific function description, just ask and I will add it. I will go through the necessary ones.
- glBindBuffer: This function will activate a given buffer and make it the active buffer (Binding). It takes two parameters: Target and Buffer. The Target will tell OpenGL what kind of buffer is being bound. Buffer is the ID of the buffer being bound. During interception (as you will see later), the ID is stored and the type of buffer is stored for later. Target can be a vertex, texture, index, etc.. buffer.
- glBindTexture: This function will Bind a texture to memory. The bound texture will become the active texture and all operations done after this call will be applied to that texture. To unbind a texture, its ID is set to NULL. This function is quite similar to glBindBuffer. During interception, we check if a 2DTex or Rect is being bound. If so then we do a lookup on its texture ID to find out its corresponding Checksum/Item ID.
- glBufferDataARB:
This function takes 3 parameters. The first is the type of buffer. Size is the size of the buffer in bytes. Data is a const void pointer to an array holding all the info that will be passed to the graphics card. Usage is whether the buffer's data is dynamic, static, etc.. Basically defines how the buffer can be used.
This function is used to fill the currently bound buffer with data. Whether it be vertex data, texture data, index data, etc.. During interception, this function's parameters are used to generate/checksum MODEL ID's.
- glBufferSubDataARB: This function is the same as above, except that it allows you to change/fill only a specific range of the currently bound buffer. This is also intercepted in-case an object is either animating or changing its data. With a range of 0 to "size" this function is exactly the same as the one above it.
- glCallList:
This function is equivalent to an array. The difference is that this can hold a sequence of commands and current data/bound textures/buffers, etc. When a List is called, whatever it holds is pushed to the graphics card!
This function has 1 parameter. Its parameter is the ID of the list that holds all the data (Lists are made using GenList and delete using DeleteList). During Interception, this function is used to Identify which list is currently being used. Font characters are usually generated in a list. One list per character. Using the ID, I've used this to figure out which Letter is currently being rendered to the screen and I store that in a vector/array.
- glColor4ub: This function is used for colouring all sorts of things. OpenGL is what we call StateMachine API calling. This means that the colour that will be used is the one that was last passed to this or any other colour functions.
Example:
glColor3i(255, 255, 0);
//Render some text here.. It will be the same as the colour above.
//Render some more text.. It will have the same colour as the above.
glColor3i(0, 255, 0); //This will change the colour of whatever is rendered AFTER this call.. and so on..During interception, this function is used to store the last colour used. It is also used to detect font colours and font shadow colours.- glDisable/glEnable: This function will enable/disable different state machines. Example:
glDisable(GL_TRIANGLES); //This will turn off the rendering of triangles.. etc. Usually used to turn off blending/depth testing.During interception, I've used this to tell when a new set of fonts/text are being rendered inorder to tell when there is a new line or a space between characters.
- glDrawElements:
This function has quite a few paramters. It is used to draw out the currently bound buffer to the screen! The important paramters are its count which gives a count of the amount of indices being rendered and a pointer to a buffer holding an array of all indices.
During interception.. Well at first, I used this to get every vertex point on a model. I've removed that for portability purposes (for now at least). Currently it is used to iterate a stored list of buffers and figure out which ones are currently being rendered. If it is being rendered then make it render in wireframe mode or else fill it!
- glEnd: This function is actually quite important. Here's why. When a texture or anything is being rendered, most of the time it has to be rendered between a call to glBegin and glEnd. glBegin will define what is being rendered. glEnd will say ok.. that is finished being rendered, flush the buffer and move onto the next thing.
Example:
glBegin(GL_QUADS);
//All drawing of textures done here.. glVertex2i, glVertex2f, etc..
glEnd();
During interception, this function is used to reset variables, detect when things are done rendering so that we can store the final product. It is used to determine when Items/textures are finished rendering so that I can add all the info collected about that item to a vector of structs(data holders).- glEndList: This function is used to let OpenGL know that you are finished generating a list. During interception, this function is used to figure out when a list if finished being generated and to store its data in an array/vector.
- glGenLists: This function is used to generate a list. During interception, it is used to determine the size of the list. This is important to determine if fonts are being buffered. How? Well this function takes 1 parameter. The amount of Lists that will be created in the next couple calls. Fonts usually have 256 characters right? Well when the amount = 256, then we start recording the ID's of each list! This lets us keep track of all lists of fonts.
- glMultiTexCoord2fARB: This function isn't too important for RS. It just allows you to specify coordinates for the current texture or Texture0/1. During interception, it is used to figure out if the compass is being rendered to the screen. The compass is a texture too but it is actually bound to multiple textures first before it is rendered and that is the usage of this function. To specify where to merge textures.
- glNewList: This function will generate a list. The amount of calls to this function depends on how many lists you said you'd need in glGenLists. Thus if glGenLists was passed 256, we know that the next 256 NewList calls are 256 characters. 1 for each char. During interception, I use this function to tell when a new list is being generated and to log the calls inbetween.
- glPopMatrix: This function is possibly one of the most useful/most important ones. This function will pop the current matrix OFF the stack and the matrix on top will be the default identity matrix. Push matrix is the counterpart to this function which will push the current matrix onto the stack! During interception, I use this function to determine when things are finished being pushed from the graphics card to the screen and to then project all the point data to the screen. Basically project 3D stuff to 2D before it touches the screen.
- glTexCoord2f:
This function is used to tell opengl which portion of a texture should be used when rendering. During interception, this is used to store which portions of a font is being rendered. Whether it be a half a letter, a whole letter, a pixel more, etc.. Think of this as the crop tool in photoshop. You choose what parts you want.
- glTexImage2D: This function is also very important as its parameters are exactly what we want. This function will take a buffer holding bytes that describe a texture and give it an ID, size, and format. This basically tells opengl how to read the texture. During interception, I check if the texture being stored is a rectangle or 2DTex. If it is, Run the checksum on its buffer. Store its checksum and other info in a multimap which will be our lookup table! This is lookup table will only add/remove ID's if a texture was deleted or does not already exist! This is the KEY to our SPEED increase in texture reading!
- glTranslatef: This function is actually quite complicated as it depends on if the current matrix is row or column major. Well that's all user implemented. It will take the current matrix and multiply it by its parameters (X, Y, Z).
Anything rendered before this call, will translated on the screen by this function.
Example:
glCallList(2141); //A List containing a letter. Lets assume letter A.
glColor4ub(255, 255, 0, 255); //Give the font character some colour.
glTranslatef(265, 20, 0); //Draw the font at this location. Aka translate it.
During interception, it's used as in the above example. To figure out the position of font characters! Aka each letter. - glVertex2f: The vertex of what part of a model/texture should be rendered. During interception these paramters are only stored. Not really used.
- glVertex2i:
This function has two paramters. X, and Y. This function is used to tell opengl where to draw. The current raster position. During interception, the parameters are saved to figure out where fonts are within their respective font textures and where/position of Textures are being rendered on the screen.
- glVertexPointer:
This function gives opengl a pointer to an array of vertices. At first this was hooked inorder to determine each vertex of any model. This was not cross card compatible and removed. Currently, during interception it is just used to add all the collected model data to an array/vector.
- wglGetProcAddress: This function is quite important. Infact this function is equivalent to the amount of unexported functions that we decide to use. For functions that aren't exported, they have to go through this function first! This function will then return a pointer to the address where the unexported function is located. You then use that function pointer and pass it parameters to use the function you wish to use.
During interception, this function is used to make our pointers valid and to hook non-default exported functions.
- wglSwapBuffers:
This function is very very very important in OpenGL. There isn't a single program out there that does not use this or SwapBuffers. Well that is if the program uses multiple buffers (double/tripple buffering) then it must use this function! During interception, this is used to call our custom functions right before all the data is pushed to the screen. Inorder words, OpenGL will render everything to a buffer. When this function is called, that entire buffer is scraped and push straight to the screen all at once! This makes rendering extremely fast rather than doing one by one!
When hooked, this is used to communicate with the shared memory between OpenGL32 and Simba. It is also used to clear all our collected data so that we don't use too much memory and so that we don't overflow, etc.. It is used to draw to the screen as well and to display info such as ID's, Angles, etc!
