|
32 bit TGA Graphics on HUD
|
|
The simple bit
|
The first thing we need to do is to load tga textures into the system. Fortunately Quake2 already understands the tga file format, it uses it for the skybox images
and also writes tga files for screenshots. All we need to do is persuade it to use the format for pictures as well.
Look at this function Draw_FindPic in gl_draw.c.
|
image_t *Draw_FindPic (char *name)
{
image_t *gl;
char fullname[MAX_QPATH];
if (name[0] != '/' && name[0] != '\\')
{
Com_sprintf (fullname, sizeof(fullname), "pics/%s.pcx", name);
gl = GL_FindImage (fullname, it_pic);
}
else
gl = GL_FindImage (name+1, it_pic);
return gl;
}
|
This is the function which is called when the system wants to get an image to render for the hud or for the menu system. Normally the name parameter is given without a path
or an extension, and Quake2 looks for a .pcx file in the pics directory. The function GL_FindImage will search the image cache to check if the image has already been
loaded. If not the disk and .pak files are searched. If the image can be found, a pointer to its loaded data is returned, otherwise GL_FindImage returns null.
All we need to do here is to make it look for .tga files first. If it finds one, then all well and good, otherwise we'll go and look for a .pcx.
Change the function like so:
|
image_t *Draw_FindPic (char *name)
{
image_t *gl;
char fullname[MAX_QPATH];
if (name[0] != '/' && name[0] != '\\')
{
Com_sprintf (fullname, sizeof(fullname), "pics/%s.tga", name); // c14 change this
gl = GL_FindImage (fullname, it_pic);
if (!gl) { // c14 these 4 lines here
Com_sprintf (fullname, sizeof(fullname), "pics/%s.pcx", name);
gl = GL_FindImage (fullname, it_pic);
}
}
else
gl = GL_FindImage (name+1, it_pic);
return gl;
}
|
|
Blending
|
|
Blending is simple enough. Further down in gl_draw.c find the function Draw_Pic.
|
void Draw_Pic (int x, int y, char *pic)
{
image_t *gl;
gl = Draw_FindPic (pic);
if (!gl)
{
ri.Con_Printf (PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
if (scrap_dirty)
Scrap_Upload ();
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglDisable (GL_ALPHA_TEST);
GL_Bind (gl->texnum);
qglBegin (GL_QUADS);
qglTexCoord2f (gl->sl, gl->tl);
qglVertex2f (x, y);
qglTexCoord2f (gl->sh, gl->tl);
qglVertex2f (x+gl->width, y);
qglTexCoord2f (gl->sh, gl->th);
qglVertex2f (x+gl->width, y+gl->height);
qglTexCoord2f (gl->sl, gl->th);
qglVertex2f (x, y+gl->height);
qglEnd ();
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglEnable (GL_ALPHA_TEST);
}
|
This function calls Draw_FindPic to get a pointer to the image information, binds the texture and draws a rectangle on the screen.
x and y are the screen coordinate for the image, gl->width and gl->height are the dimensions of the image to be drawn and gl->sl, gl->tl, gl->sh and gl->th are
the texture coordinates.
The part we are interested in is the lines where we Enable and Disable GL_ALPHA_TEST. Change the function to this:
|
void Draw_Pic (int x, int y, char *pic)
{
image_t *gl;
gl = Draw_FindPic (pic);
if (!gl)
{
ri.Con_Printf (PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
if (scrap_dirty)
Scrap_Upload ();
if (gl->paletted) {
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglDisable (GL_ALPHA_TEST);
}
else {
qglDisable (GL_ALPHA_TEST);
qglEnable (GL_BLEND);
}
GL_Bind (gl->texnum);
qglBegin (GL_QUADS);
qglTexCoord2f (gl->sl, gl->tl);
qglVertex2f (x, y);
qglTexCoord2f (gl->sh, gl->tl);
qglVertex2f (x+gl->width, y);
qglTexCoord2f (gl->sh, gl->th);
qglVertex2f (x+gl->width, y+gl->height);
qglTexCoord2f (gl->sl, gl->th);
qglVertex2f (x, y+gl->height);
qglEnd ();
if (gl->paletted) {
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglEnable (GL_ALPHA_TEST);
}
else {
qglEnable (GL_ALPHA_TEST);
qglDisable (GL_BLEND);
}
}
|
Now we are checking to see if the image has a palette, if so we will handle it as before, otherwise we will enable blending.
If you're not very familiar with opengl, you might be a little confused as to why we disable GL_ALPHA_TEST. When enabled GL_ALPHA_TEST examines the alpha value of
a pixel compares it with a reference value. Depending on whether the pixel to be rendered has a higher or lower alpha than the reference, the pixel either will or will
not be rendered. This is an on-off test with no shades between. Instead, we want to enable GL_BLEND so that the pixels are overlaid on the screen in proportion to
their alpha.
Now if you look just above this function you will find Draw_StretchPic. You need to make similar changes there.
|
void Draw_StretchPic (int x, int y, int w, int h, char *pic)
{
image_t *gl;
gl = Draw_FindPic (pic);
if (!gl)
{
ri.Con_Printf (PRINT_ALL, "Can't find pic: %s\n", pic);
return;
}
if (scrap_dirty)
Scrap_Upload ();
if (gl->paletted) {
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglDisable (GL_ALPHA_TEST);
}
else {
qglDisable (GL_ALPHA_TEST);
qglEnable (GL_BLEND);
}
GL_Bind (gl->texnum);
qglBegin (GL_QUADS);
qglTexCoord2f (gl->sl, gl->tl);
qglVertex2f (x, y);
qglTexCoord2f (gl->sh, gl->tl);
qglVertex2f (x+w, y);
qglTexCoord2f (gl->sh, gl->th);
qglVertex2f (x+w, y+h);
qglTexCoord2f (gl->sl, gl->th);
qglVertex2f (x, y+h);
qglEnd ();
if (gl->paletted) {
if ( ( ( gl_config.renderer == GL_RENDERER_MCD ) || ( gl_config.renderer & GL_RENDERER_RENDITION ) ) && !gl->has_alpha)
qglEnable (GL_ALPHA_TEST);
}
else {
qglEnable (GL_ALPHA_TEST);
qglDisable (GL_BLEND);
}
}
|
|
The first problem
|
What we have done so far does what it's supposed to, but there are a couple of issues that need to be fixed. The first problem is that unless you replace ALL of your
.pcx files with .tga, you will probable find a serious drop in framerate. I'll explain... Lets say that we have a file wflag1.tga that we intend to draw on the hud, the following happens:
- We call Draw_Pic with the name 'wflag1'
- Draw_Pic calls Draw_FindPic
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.tga
- GL_FindImage searches the image cache and cannot find the file
- GL_FindImage searches the disk and .pak files and loads the file
Now in the next frame, the following happens:
- We call Draw_Pic with the name 'wflag1'
- Draw_Pic calls Draw_FindPic
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.tga
- GL_FindImage searches the image cache and finds the file already there
This is perfect. But consider the process if we do not have a wflag1.tga, and are relying on wflag1.pcx instead:
- We call Draw_Pic with the name 'wflag1'
- Draw_Pic calls Draw_FindPic
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.tga
- GL_FindImage searches the image cache and cannot find the file
- GL_FindImage searches the disk and .pak files and cannot find it
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.pcx
- GL_FindImage searches the image cache and cannot find the file
- GL_FindImage searches the disk and .pak files and loads the file
And in the next frame:
- We call Draw_Pic with the name 'wflag1'
- Draw_Pic calls Draw_FindPic
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.tga
- GL_FindImage searches the image cache and cannot find the file
- GL_FindImage searches the disk and .pak files and cannot find it
- Draw_FindPic calls GL_FindImage to look for pics/wflag1.pcx
- GL_FindImage searches the image cache and finds the file already there
Each frame, the system is searching the disk for the non-existant .tga file even though we have a perfectly good .pcx already loaded in memory.
We will deal with this by changing the way we search the image cache, relying solely on file name and ignoring the extension.
The function we want is GL_FindImage
|
image_t *GL_FindImage (char *name, imagetype_t type)
{
image_t *image;
int i, len;
byte *pic, *palette;
int width, height;
if (!name)
return NULL; // ri.Sys_Error (ERR_DROP, "GL_FindImage: NULL name");
len = strlen(name);
if (len<5)
return NULL; // ri.Sys_Error (ERR_DROP, "GL_FindImage: bad name: %s", name);
// look for it
for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
{
if (!strcmp(name, image->name))
{
image->registration_sequence = registration_sequence;
return image;
}
}
|
|
Change the code as follows, so that we only campare names up to but not including the extension.
|
image_t *GL_FindImage (char *name, imagetype_t type)
{
image_t *image;
int i, len;
byte *pic, *palette;
int width, height;
if (!name)
return NULL; // ri.Sys_Error (ERR_DROP, "GL_FindImage: NULL name");
len = strlen(name);
if (len<5)
return NULL; // ri.Sys_Error (ERR_DROP, "GL_FindImage: bad name: %s", name);
// look for it
for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
{
if (!strncmp(name, image->name, len-3))
{
image->registration_sequence = registration_sequence;
return image;
}
}
|
|
The second problem
|
|
You might never come across the second problem, but if you want a transparent console, then you probably will. The image for the console is pics/conback.pcx.
It is drawn part of the screen if you bring it down during the game and if you add a pics/conback.tga you can make it partially transparent. The problem comes
when you are not in a game. Before the game starts, or if you disconnect from a game, the console covers the whole screen, but if the system doesn't have anything to
render behind it, you will be able to see through the console to the previous contents of the frame buffers. This is ugly and distracting.
The console is drawn by the function SCR_DrawConsole in the file cl_scrn.c. Note that this is part of the quake2.exe, not ref_gl.dll.
|
void SCR_DrawConsole (void)
{
Con_CheckResize ();
if (cls.state == ca_disconnected || cls.state == ca_connecting)
{ // forced full screen console
Con_DrawConsole (1.0);
return;
}
if (cls.state != ca_active || !cl.refresh_prepped)
{ // connected, but can't render
Con_DrawConsole (0.5);
re.DrawFill (0, viddef.height/2, viddef.width, viddef.height/2, 0);
return;
}
if (scr_con_current)
{
Con_DrawConsole (scr_con_current);
}
else
{
if (cls.key_dest == key_game || cls.key_dest == key_message)
Con_DrawNotify (); // only draw notify in game
}
}
|
|
You'll notice in the middle of this function, that if the client is connected, but for some reason not in a position to render, such as while it is loading the first map,
then the console is drawn halfway down the screen, and the remainder of the screen is filled in black. All we need to do is ensure that the entire screen is filled with
black before we draw the console, but only when we're not in game:
|
void SCR_DrawConsole (void)
{
Con_CheckResize ();
if (cls.state == ca_disconnected || cls.state == ca_connecting)
{ // forced full screen console
re.DrawFill (0, 0, viddef.width, viddef.height, 0); // c14 add this line
Con_DrawConsole (1.0);
return;
}
if (cls.state != ca_active || !cl.refresh_prepped)
{ // connected, but can't render
re.DrawFill (0, 0, viddef.width, viddef.height, 0); // c14 change this line and it needs moving to before Con_DrawConsole
Con_DrawConsole (0.5);
return;
}
if (scr_con_current)
{
Con_DrawConsole (scr_con_current);
}
else
{
if (cls.key_dest == key_game || cls.key_dest == key_message)
Con_DrawNotify (); // only draw notify in game
}
}
|
The finished product
|