blob: 775ac77f0b8186fba2cb6f6591111defe43e49ed [file] [log] [blame]
/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// draw.c -- this is the only file outside the refresh that touches the
// vid buffer
#include "quakedef.h"
#define GL_COLOR_INDEX8_EXT 0x80E5
cvar_t gl_nobind = CVAR2("gl_nobind", "0");
cvar_t gl_max_size = CVAR2("gl_max_size", "1024");
cvar_t gl_picmip = CVAR2("gl_picmip", "0");
byte *draw_chars; // 8*8 graphic characters
qpic_t *draw_disc;
qpic_t *draw_backtile;
int translate_texture;
int char_texture;
typedef struct
{
int texnum;
float sl, tl, sh, th;
} glpic_t;
typedef union
{
qpic_t qpic;
struct {
// First part is from qpic
int width;
int height;
glpic_t glpic;
} g;
} packedGlpic_t;
typedef union
{
byte buffer[sizeof(qpic_t) + sizeof(glpic_t)];
packedGlpic_t pics;
} conback_t;
conback_t conbackUnion;
#define conback_buffer (conbackUnion.buffer)
packedGlpic_t *conback = &conbackUnion.pics;
int gl_lightmap_format = 4;
int gl_solid_format = 3;
int gl_alpha_format = 4;
#if 1 // Standard defaults
int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
int gl_filter_max = GL_LINEAR;
#else
int gl_filter_min = GL_NEAREST_MIPMAP_NEAREST;
int gl_filter_max = GL_NEAREST;
#endif
int texels;
typedef struct
{
int texnum;
char identifier[64];
int width, height;
qboolean mipmap;
} gltexture_t;
#define MAX_GLTEXTURES 1024
gltexture_t gltextures[MAX_GLTEXTURES];
int numgltextures;
// GlQuake creates textures, but never deletes them. This approach works fine on
// computers with lots of RAM and/or swap, but not so well on our swapless
// RAM-constrained system.
//
// We work around this problem by adding a level of indirection. We
// hook GL_LoadTexture to store enough information to recreate a texture.
// Then we hook GL_BindTexture to consult a table to see whether a texture
// is currently in memory or not. If it isn't, we throw out some other
// texture and bring the required texture back into memory. In this way
// we can limit the working set of textures.
//
// The texture data is stored in a memory-mapped file that is backed by
// a file on the sd card. It is recreated each time the game is run. We
// don't bother deleting it.
#define USE_TEXTURE_STORE
#ifdef USE_TEXTURE_STORE
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
int errno_portable();
// Allow named textures to be evicted from memory.
#define TEXTURE_STORE_NAME "glquake/texture.store"
class textureStore {
private:
static const GLuint UNUSED = (GLuint) -2;
static const GLuint PAGED_OUT = (GLuint) -1;
struct entry
{
entry* next;
entry* prev;
GLuint real_texnum; // UNUSED, PAGED_OUT
byte* pData; // 0 ==> not created by us.
size_t size;
qboolean alpha;
int width;
int height;
qboolean mipmap;
entry() {
next = 0;
prev = 0;
real_texnum = UNUSED;
pData = 0;
}
void unlink() {
if (next) {
next->prev = prev;
}
if (prev) {
prev->next = next;
}
next = 0;
prev = 0;
}
void insertBefore(entry* e){
if (e) {
prev = e->prev;
if ( prev ) {
prev->next = this;
}
next = e;
e->prev = this;
}
else {
prev = 0;
next = 0;
}
}
};
public:
static textureStore* get() {
if (g_pTextureCache == 0) {
g_pTextureCache = new textureStore();
}
return g_pTextureCache;
}
// Equivalent of glBindTexture, but uses the virtual texture table
void bind(int virtTexNum) {
if ( (unsigned int) virtTexNum >= TEXTURE_STORE_NUM_TEXTURES) {
Sys_Error("not in the range we're managing");
}
mBoundTextureID = virtTexNum;
entry* e = &mTextures[virtTexNum];
if ( e->real_texnum == UNUSED) {
glGenTextures( 1, &e->real_texnum);
}
if ( e->pData == 0) {
glBindTexture(GL_TEXTURE_2D, e->real_texnum);
return;
}
update(e);
}
void update(entry* e)
{
// Update the "LRU" part of the cache
unlink(e);
e->insertBefore(mFirst);
mFirst = e;
if (! mLast) {
mLast = e;
}
if (e->real_texnum == PAGED_OUT ) {
// Create a real texture
// Make sure there is enough room for this texture
ensure(e->size);
glGenTextures( 1, &e->real_texnum);
glBindTexture(GL_TEXTURE_2D, e->real_texnum);
GL_Upload8 (e->pData, e->width, e->height, e->mipmap,
e->alpha);
}
else {
glBindTexture(GL_TEXTURE_2D, e->real_texnum);
}
}
// Create a texture, and remember the data so we can create
// it again later.
void create(int width, int height, byte* data, qboolean mipmap,
qboolean alpha) {
int size = width * height;
if (size + mLength > mCapacity) {
Sys_Error("Ran out of virtual texture space. %d", size);
};
entry* e = &mTextures[mBoundTextureID];
// Call evict in case the currently bound texture id is already
// in use. (Shouldn't happen in Quake.)
// To Do: reclaim the old texture memory from the virtual memory.
evict(e);
e->alpha = alpha;
e->pData = mBase + mLength;
memcpy(e->pData, data, size);
e->size = size;
e->width = width;
e->height = height;
e->mipmap = mipmap;
e->real_texnum = PAGED_OUT;
mLength += size;
update(e);
}
// Re-upload the current textures because we've been reset.
void rebindAll() {
grabMagicTextureIds();
for (entry* e = mFirst; e; e = e->next ) {
if (! (e->real_texnum == UNUSED || e->real_texnum == PAGED_OUT)) {
glBindTexture(GL_TEXTURE_2D, e->real_texnum);
if (e->pData) {
GL_Upload8 (e->pData, e->width, e->height, e->mipmap,
e->alpha);
}
}
}
}
private:
textureStore() {
grabMagicTextureIds();
mFirst = 0;
mLast = 0;
mTextureCount = 0;
char fullpath[MAX_OSPATH];
sprintf(fullpath, "%s/%s", com_gamedir, TEXTURE_STORE_NAME);
mFileId = open(fullpath, O_RDWR | O_CREAT, 0666);
if ( mFileId == -1 ) {
Sys_Error("Could not open texture store file %s: %d", fullpath,
errno_portable());
}
if (-1 == lseek(mFileId, TEXTURE_STORE_SIZE-1, SEEK_SET)) {
Sys_Error("Could not extend the texture store file size. %d",
errno_portable());
}
char end;
end = 0;
if (-1 == write(mFileId, &end, 1)) {
Sys_Error("Could not write last byte of the texture store file. %d",
errno_portable());
}
mBase = (byte*) mmap((caddr_t)0, TEXTURE_STORE_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE, mFileId, 0);
if (mBase == (byte*) -1) {
Sys_Error("Could not mmap file %s: %d", fullpath, errno_portable());
}
mLength = 0;
mCapacity = TEXTURE_STORE_SIZE;
mRamUsed = 0;
mRamSize = LIVE_TEXTURE_LIMIT;
}
~textureStore() {
munmap(mBase, mCapacity);
COM_CloseFile(mFileId);
}
void grabMagicTextureIds() {
// reserve these two texture ids.
glBindTexture(GL_TEXTURE_2D, UNUSED);
glBindTexture(GL_TEXTURE_2D, PAGED_OUT);
}
void unlink(entry* e) {
if (e == mFirst) {
mFirst = e->next;
}
if (e == mLast) {
mLast = e->prev;
}
e->unlink();
}
void ensure(int size) {
while ( mRamSize - mRamUsed < (unsigned int) size) {
entry* e = mLast;
if(! e) {
Sys_Error("Ran out of entries");
return;
}
evict(e);
}
mRamUsed += size;
}
void evict(entry* e) {
unlink(e);
if ( e->pData ) {
glDeleteTextures(1, &e->real_texnum);
e->real_texnum = PAGED_OUT;
mRamUsed -= e->size;
}
}
static const size_t TEXTURE_STORE_SIZE = 16 * 1024 * 1024;
static const size_t LIVE_TEXTURE_LIMIT = 1 * 1024 * 1024;
static const size_t TEXTURE_STORE_NUM_TEXTURES = 512;
int mFileId;
byte* mBase; // Base address of the memory mapped file
size_t mLength; // How much of the mm file we are currently using
size_t mCapacity; // Total size of the memory mapped file
// Keep track of texture RAM.
size_t mRamUsed;
size_t mRamSize;
// The virtual textures
entry mTextures[MAX_GLTEXTURES];
entry* mFirst; // LRU queue
entry* mLast;
size_t mTextureCount; // How many virtual textures have been allocated
static textureStore* g_pTextureCache;
int mBoundTextureID;
};
textureStore* textureStore::g_pTextureCache;
#endif
void GL_Bind (int texnum)
{
if (gl_nobind.value)
texnum = char_texture;
if (currenttexture == texnum)
return;
currenttexture = texnum;
#ifdef _WIN32
bindTexFunc (GL_TEXTURE_2D, texnum);
#else
#ifdef USE_TEXTURE_STORE
textureStore::get()->bind(texnum);
#else
glBindTexture(GL_TEXTURE_2D, texnum);
#endif
#endif
}
/*
=============================================================================
scrap allocation
Allocate all the little status bar obejcts into a single texture
to crutch up stupid hardware / drivers
=============================================================================
*/
#define MAX_SCRAPS 2
#define BLOCK_WIDTH 256
#define BLOCK_HEIGHT 256
int scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH];
byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT*4];
qboolean scrap_dirty;
int scrap_texnum;
// returns a texture number and the position inside it
int Scrap_AllocBlock (int w, int h, int *x, int *y)
{
int i, j;
int best, best2;
int bestx;
int texnum;
for (texnum=0 ; texnum<MAX_SCRAPS ; texnum++)
{
best = BLOCK_HEIGHT;
for (i=0 ; i<BLOCK_WIDTH-w ; i++)
{
best2 = 0;
for (j=0 ; j<w ; j++)
{
if (scrap_allocated[texnum][i+j] >= best)
break;
if (scrap_allocated[texnum][i+j] > best2)
best2 = scrap_allocated[texnum][i+j];
}
if (j == w)
{ // this is a valid spot
*x = i;
*y = best = best2;
}
}
if (best + h > BLOCK_HEIGHT)
continue;
for (i=0 ; i<w ; i++)
scrap_allocated[texnum][*x + i] = best + h;
return texnum;
}
Sys_Error ("Scrap_AllocBlock: full");
return 0;
}
int scrap_uploads;
void Scrap_Upload (void)
{
int texnum;
scrap_uploads++;
for (texnum=0 ; texnum<MAX_SCRAPS ; texnum++) {
GL_Bind(scrap_texnum + texnum);
GL_Upload8 (scrap_texels[texnum], BLOCK_WIDTH, BLOCK_HEIGHT, false, true);
}
scrap_dirty = false;
}
//=============================================================================
/* Support Routines */
typedef struct cachepic_s
{
char name[MAX_QPATH];
qpic_t pic;
byte padding[32]; // for appended glpic
} cachepic_t;
#define MAX_CACHED_PICS 128
cachepic_t menu_cachepics[MAX_CACHED_PICS];
int menu_numcachepics;
byte menuplyr_pixels[4096];
int pic_texels;
int pic_count;
/*
================
GL_LoadPicTexture
================
*/
int GL_LoadPicTexture (qpic_t *pic)
{
return GL_LoadTexture ("", pic->width, pic->height, pic->data, false, true);
}
qpic_t *Draw_PicFromWad (const char *name)
{
packedGlpic_t *pp;
pp = (packedGlpic_t*) W_GetLumpName (name);
qpic_t* p = & pp->qpic;
glpic_t* gl = & pp->g.glpic;
// load little ones into the scrap
if (p->width < 64 && p->height < 64)
{
int x, y;
int i, j, k;
int texnum;
texnum = Scrap_AllocBlock (p->width, p->height, &x, &y);
scrap_dirty = true;
k = 0;
for (i=0 ; i<p->height ; i++)
for (j=0 ; j<p->width ; j++, k++)
scrap_texels[texnum][(y+i)*BLOCK_WIDTH + x + j] = p->data[k];
texnum += scrap_texnum;
gl->texnum = texnum;
gl->sl = (x+0.01)/(float)BLOCK_WIDTH;
gl->sh = (x+p->width-0.01)/(float)BLOCK_WIDTH;
gl->tl = (y+0.01)/(float)BLOCK_WIDTH;
gl->th = (y+p->height-0.01)/(float)BLOCK_WIDTH;
pic_count++;
pic_texels += p->width*p->height;
}
else
{
gl->texnum = GL_LoadPicTexture (p);
gl->sl = 0;
gl->sh = 1;
gl->tl = 0;
gl->th = 1;
}
return p;
}
/*
================
Draw_CachePic
================
*/
qpic_t *Draw_CachePic (const char *path)
{
cachepic_t *pic;
int i;
qpic_t *dat;
glpic_t *gl;
for (pic=menu_cachepics, i=0 ; i<menu_numcachepics ; pic++, i++)
if (!strcmp (path, pic->name))
return &pic->pic;
if (menu_numcachepics == MAX_CACHED_PICS)
Sys_Error ("menu_numcachepics == MAX_CACHED_PICS");
menu_numcachepics++;
strcpy (pic->name, path);
//
// load the pic from disk
//
dat = (qpic_t *)COM_LoadTempFile (path);
if (!dat)
Sys_Error ("Draw_CachePic: failed to load %s", path);
SwapPic (dat);
// HACK HACK HACK --- we need to keep the bytes for
// the translatable player picture just for the menu
// configuration dialog
if (!strcmp (path, "gfx/menuplyr.lmp"))
memcpy (menuplyr_pixels, dat->data, dat->width*dat->height);
pic->pic.width = dat->width;
pic->pic.height = dat->height;
glpic_t temp;
gl = &temp;
gl->texnum = GL_LoadPicTexture (dat);
gl->sl = 0;
gl->sh = 1;
gl->tl = 0;
gl->th = 1;
memcpy(pic->pic.data, &temp, sizeof(temp));
return &pic->pic;
}
void Draw_CharToConback (int num, byte *dest)
{
int row, col;
byte *source;
int drawline;
int x;
row = num>>4;
col = num&15;
source = draw_chars + (row<<10) + (col<<3);
drawline = 8;
while (drawline--)
{
for (x=0 ; x<8 ; x++)
if (source[x] != 255)
dest[x] = 0x60 + source[x];
source += 128;
dest += 320;
}
}
typedef struct
{
const char *name;
int minimize, maximize;
} glmode_t;
glmode_t modes[] = {
{"GL_NEAREST", GL_NEAREST, GL_NEAREST},
{"GL_LINEAR", GL_LINEAR, GL_LINEAR},
{"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
{"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
{"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
{"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
};
/*
===============
Draw_TextureMode_f
===============
*/
void Draw_TextureMode_f (void)
{
int i;
gltexture_t *glt;
if (Cmd_Argc() == 1)
{
for (i=0 ; i< 6 ; i++)
if (gl_filter_min == modes[i].minimize)
{
Con_Printf ("%s\n", modes[i].name);
return;
}
Con_Printf ("current filter is unknown???\n");
return;
}
for (i=0 ; i< 6 ; i++)
{
if (!Q_strcasecmp (modes[i].name, Cmd_Argv(1) ) )
break;
}
if (i == 6)
{
Con_Printf ("bad filter name\n");
return;
}
gl_filter_min = modes[i].minimize;
gl_filter_max = modes[i].maximize;
// change all the existing mipmap texture objects
for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
{
if (glt->mipmap)
{
GL_Bind (glt->texnum);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
}
}
/*
===============
Draw_Init
===============
*/
void Draw_Init (void)
{
int i;
qpic_t *cb;
byte *dest, *src;
int x, y;
char ver[40];
glpic_t *gl;
int start;
byte *ncdata;
int f, fstep;
Cvar_RegisterVariable (&gl_nobind);
Cvar_RegisterVariable (&gl_max_size);
Cvar_RegisterVariable (&gl_picmip);
// 3dfx can only handle 256 wide textures
if (!Q_strncasecmp ((char *)gl_renderer, "3dfx",4) ||
strstr((char *)gl_renderer, "Glide"))
Cvar_Set ("gl_max_size", "256");
Cmd_AddCommand ("gl_texturemode", &Draw_TextureMode_f);
// load the console background and the charset
// by hand, because we need to write the version
// string into the background before turning
// it into a texture
draw_chars = (byte*) W_GetLumpName ("conchars");
for (i=0 ; i<256*64 ; i++)
if (draw_chars[i] == 0)
draw_chars[i] = 255; // proper transparent color
// now turn them into textures
char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true);
start = Hunk_LowMark();
cb = (qpic_t *)COM_LoadTempFile ("gfx/conback.lmp");
if (!cb)
Sys_Error ("Couldn't load gfx/conback.lmp");
SwapPic (cb);
// hack the version number directly into the pic
#if defined(__linux__)
sprintf (ver, "(Linux %2.2f, gl %4.2f) %4.2f", (float)LINUX_VERSION, (float)GLQUAKE_VERSION, (float)VERSION);
#else
sprintf (ver, "(gl %4.2f) %4.2f", (float)GLQUAKE_VERSION, (float)VERSION);
#endif
dest = cb->data + 320*186 + 320 - 11 - 8*strlen(ver);
y = strlen(ver);
for (x=0 ; x<y ; x++)
Draw_CharToConback (ver[x], dest+(x<<3));
#if 0
conback->width = vid.conwidth;
conback->height = vid.conheight;
// scale console to vid size
dest = ncdata = Hunk_AllocName(vid.conwidth * vid.conheight, "conback");
for (y=0 ; y<vid.conheight ; y++, dest += vid.conwidth)
{
src = cb->data + cb->width * (y*cb->height/vid.conheight);
if (vid.conwidth == cb->width)
memcpy (dest, src, vid.conwidth);
else
{
f = 0;
fstep = cb->width*0x10000/vid.conwidth;
for (x=0 ; x<vid.conwidth ; x+=4)
{
dest[x] = src[f>>16];
f += fstep;
dest[x+1] = src[f>>16];
f += fstep;
dest[x+2] = src[f>>16];
f += fstep;
dest[x+3] = src[f>>16];
f += fstep;
}
}
}
#else
conback->g.width = cb->width;
conback->g.height = cb->height;
ncdata = cb->data;
#endif
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl = &conback->g.glpic;
gl->texnum = GL_LoadTexture ("conback", conback->g.width, conback->g.height, ncdata, false, false);
gl->sl = 0;
gl->sh = 1;
gl->tl = 0;
gl->th = 1;
conback->g.width = vid.width;
conback->g.height = vid.height;
// free loaded console
Hunk_FreeToLowMark(start);
// save a texture slot for translated picture
translate_texture = texture_extension_number++;
// save slots for scraps
scrap_texnum = texture_extension_number;
texture_extension_number += MAX_SCRAPS;
//
// get the other pics we need
//
draw_disc = Draw_PicFromWad ("disc");
draw_backtile = Draw_PicFromWad ("backtile");
}
/*
================
Draw_Character
Draws one 8*8 graphics character with 0 being transparent.
It can be clipped to the top of the screen to allow the console to be
smoothly scrolled off.
================
*/
void Draw_Character (int x, int y, int num)
{
byte *dest;
byte *source;
unsigned short *pusdest;
int drawline;
int row, col;
float frow, fcol, size;
if (num == 32)
return; // space
num &= 255;
if (y <= -8)
return; // totally off screen
row = num>>4;
col = num&15;
frow = row*0.0625;
fcol = col*0.0625;
size = 0.0625;
GL_Bind (char_texture);
#ifdef USE_OPENGLES
DrawQuad(x, y, 8, 8, fcol, frow, size, size);
#else
glBegin (GL_QUADS);
glTexCoord2f (fcol, frow);
glVertex2f (x, y);
glTexCoord2f (fcol + size, frow);
glVertex2f (x+8, y);
glTexCoord2f (fcol + size, frow + size);
glVertex2f (x+8, y+8);
glTexCoord2f (fcol, frow + size);
glVertex2f (x, y+8);
glEnd ();
#endif
}
/*
================
Draw_String
================
*/
void Draw_String (int x, int y, const char *str)
{
while (*str)
{
Draw_Character (x, y, *str);
str++;
x += 8;
}
}
/*
================
Draw_DebugChar
Draws a single character directly to the upper right corner of the screen.
This is for debugging lockups by drawing different chars in different parts
of the code.
================
*/
void Draw_DebugChar (char num)
{
}
/*
=============
Draw_AlphaPic
=============
*/
void Draw_AlphaPic (int x, int y, packedGlpic_t *ppic, float alpha)
{
byte *dest, *source;
unsigned short *pusdest;
int v, u;
glpic_t *gl;
if (scrap_dirty)
Scrap_Upload ();
gl = & ppic->g.glpic;
glDisable(GL_ALPHA_TEST);
glEnable (GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// glCullFace(GL_FRONT);
glColor4f (1,1,1,alpha);
GL_Bind (gl->texnum);
#ifdef USE_OPENGLES
DrawQuad(x, y, ppic->g.width, ppic->g.height, gl->sl, gl->tl, gl->sh - gl->sl, gl->th - gl->tl);
#else
glBegin (GL_QUADS);
glTexCoord2f (gl->sl, gl->tl);
glVertex2f (x, y);
glTexCoord2f (gl->sh, gl->tl);
glVertex2f (x+pic->width, y);
glTexCoord2f (gl->sh, gl->th);
glVertex2f (x+pic->width, y+pic->height);
glTexCoord2f (gl->sl, gl->th);
glVertex2f (x, y+pic->height);
glEnd ();
#endif
glColor4f (1,1,1,1);
glEnable(GL_ALPHA_TEST);
glDisable (GL_BLEND);
}
/*
=============
Draw_Pic
=============
*/
void Draw_Pic (int x, int y, qpic_t *pic)
{
byte *dest, *source;
unsigned short *pusdest;
int v, u;
glpic_t *gl;
if (scrap_dirty)
Scrap_Upload ();
glpic_t temp;
memcpy(&temp, pic->data, sizeof(temp));
gl = & temp;
glColor4f (1,1,1,1);
GL_Bind (gl->texnum);
#ifdef USE_OPENGLES
DrawQuad(x, y, pic->width, pic->height, gl->sl, gl->tl, gl->sh - gl->sl, gl->th - gl->tl);
#else
glBegin (GL_QUADS);
glTexCoord2f (gl->sl, gl->tl);
glVertex2f (x, y);
glTexCoord2f (gl->sh, gl->tl);
glVertex2f (x+pic->width, y);
glTexCoord2f (gl->sh, gl->th);
glVertex2f (x+pic->width, y+pic->height);
glTexCoord2f (gl->sl, gl->th);
glVertex2f (x, y+pic->height);
glEnd ();
#endif
}
/*
=============
Draw_TransPic
=============
*/
void Draw_TransPic (int x, int y, qpic_t *pic)
{
byte *dest, *source, tbyte;
unsigned short *pusdest;
int v, u;
if (x < 0 || (unsigned)(x + pic->width) > vid.width || y < 0 ||
(unsigned)(y + pic->height) > vid.height)
{
Sys_Error ("Draw_TransPic: bad coordinates");
}
Draw_Pic (x, y, pic);
}
/*
=============
Draw_TransPicTranslate
Only used for the player color selection menu
=============
*/
void Draw_TransPicTranslate (int x, int y, qpic_t *pic, byte *translation)
{
int v, u, c;
unsigned trans[64*64], *dest;
byte *src;
int p;
GL_Bind (translate_texture);
c = pic->width * pic->height;
dest = trans;
for (v=0 ; v<64 ; v++, dest += 64)
{
src = &menuplyr_pixels[ ((v*pic->height)>>6) *pic->width];
for (u=0 ; u<64 ; u++)
{
p = src[(u*pic->width)>>6];
if (p == 255)
dest[u] = p;
else
dest[u] = d_8to24table[translation[p]];
}
}
glTexImage2DHelper (GL_TEXTURE_2D, 0, gl_alpha_format, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glColor3f (1,1,1);
#ifdef USE_OPENGLES
DrawQuad(x, y, pic->width, pic->height, 0, 0, 1, 1);
#else
glBegin (GL_QUADS);
glTexCoord2f (0, 0);
glVertex2f (x, y);
glTexCoord2f (1, 0);
glVertex2f (x+pic->width, y);
glTexCoord2f (1, 1);
glVertex2f (x+pic->width, y+pic->height);
glTexCoord2f (0, 1);
glVertex2f (x, y+pic->height);
glEnd ();
#endif
}
/*
================
Draw_ConsoleBackground
================
*/
void Draw_ConsoleBackground (int lines)
{
int y = (vid.height * 3) >> 2;
if (lines > y)
Draw_Pic(0, lines - vid.height, &conback->qpic);
else
Draw_AlphaPic (0, lines - vid.height, conback, (float)(1.2 * lines)/y);
}
/*
=============
Draw_TileClear
This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/
typedef union ByteToInt_t {
byte b[4];
int i;
} ByteToInt;
void Draw_TileClear (int x, int y, int w, int h)
{
glColor3f (1,1,1);
ByteToInt b;
memcpy(b.b, draw_backtile->data, sizeof(b.b));
GL_Bind (b.i);
#ifdef USE_OPENGLES
DrawQuad(x, y, w, h, x/64.0, y/64.0, w/64.0, h/64.0);
#else
glBegin (GL_QUADS);
glTexCoord2f (x/64.0, y/64.0);
glVertex2f (x, y);
glTexCoord2f ( (x+w)/64.0, y/64.0);
glVertex2f (x+w, y);
glTexCoord2f ( (x+w)/64.0, (y+h)/64.0);
glVertex2f (x+w, y+h);
glTexCoord2f ( x/64.0, (y+h)/64.0 );
glVertex2f (x, y+h);
glEnd ();
#endif
}
/*
=============
Draw_Fill
Fills a box of pixels with a single color
=============
*/
void Draw_Fill (int x, int y, int w, int h, int c)
{
glDisable (GL_TEXTURE_2D);
glColor3f (host_basepal[c*3]/255.0,
host_basepal[c*3+1]/255.0,
host_basepal[c*3+2]/255.0);
#ifdef USE_OPENGLES
DrawQuad_NoTex(x, y, w, h);
#else
glBegin (GL_QUADS);
glVertex2f (x,y);
glVertex2f (x+w, y);
glVertex2f (x+w, y+h);
glVertex2f (x, y+h);
glEnd ();
#endif
glColor3f (1,1,1);
glEnable (GL_TEXTURE_2D);
}
//=============================================================================
/*
================
Draw_FadeScreen
================
*/
void Draw_FadeScreen (void)
{
glEnable (GL_BLEND);
glDisable (GL_TEXTURE_2D);
glColor4f (0, 0, 0, 0.8);
#ifdef USE_OPENGLES
DrawQuad_NoTex(0, 0, vid.width, vid.height);
#else
glBegin (GL_QUADS);
glVertex2f (0,0);
glVertex2f (vid.width, 0);
glVertex2f (vid.width, vid.height);
glVertex2f (0, vid.height);
glEnd ();
#endif
glColor4f (1,1,1,1);
glEnable (GL_TEXTURE_2D);
glDisable (GL_BLEND);
Sbar_Changed();
}
//=============================================================================
/*
================
Draw_BeginDisc
Draws the little blue disc in the corner of the screen.
Call before beginning any disc IO.
================
*/
void Draw_BeginDisc (void)
{
if (!draw_disc)
return;
#ifdef USE_OPENGLES
// !!! Implement this
#else
glDrawBuffer (GL_FRONT);
Draw_Pic (vid.width - 24, 0, draw_disc);
glDrawBuffer (GL_BACK);
#endif
}
/*
================
Draw_EndDisc
Erases the disc icon.
Call after completing any disc IO
================
*/
void Draw_EndDisc (void)
{
}
/*
================
GL_Set2D
Setup as if the screen was 320*200
================
*/
void GL_Set2D (void)
{
glViewport (glx, gly, glwidth, glheight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity ();
#ifdef USE_OPENGLES
glOrthof (0, vid.width, vid.height, 0, -99999, 99999);
#else
glOrtho (0, vid.width, vid.height, 0, -99999, 99999);
#endif
glMatrixMode(GL_MODELVIEW);
glLoadIdentity ();
glDisable (GL_DEPTH_TEST);
glDisable (GL_CULL_FACE);
glDisable (GL_BLEND);
glEnable (GL_ALPHA_TEST);
// glDisable (GL_ALPHA_TEST);
glColor4f (1,1,1,1);
}
//====================================================================
/*
================
GL_FindTexture
================
*/
int GL_FindTexture (const char *identifier)
{
int i;
gltexture_t *glt;
for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
{
if (!strcmp (identifier, glt->identifier))
return gltextures[i].texnum;
}
return -1;
}
/*
================
GL_ResampleTexture
================
*/
void GL_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight)
{
int i, j;
unsigned *inrow;
unsigned frac, fracstep;
fracstep = inwidth*0x10000/outwidth;
for (i=0 ; i<outheight ; i++, out += outwidth)
{
inrow = in + inwidth*(i*inheight/outheight);
frac = fracstep >> 1;
for (j=0 ; j<outwidth ; j+=4)
{
out[j] = inrow[frac>>16];
frac += fracstep;
out[j+1] = inrow[frac>>16];
frac += fracstep;
out[j+2] = inrow[frac>>16];
frac += fracstep;
out[j+3] = inrow[frac>>16];
frac += fracstep;
}
}
}
/*
================
GL_Resample8BitTexture -- JACK
================
*/
void GL_Resample8BitTexture (unsigned char *in, int inwidth, int inheight, unsigned char *out, int outwidth, int outheight)
{
int i, j;
unsigned char *inrow;
unsigned frac, fracstep;
fracstep = inwidth*0x10000/outwidth;
for (i=0 ; i<outheight ; i++, out += outwidth)
{
inrow = in + inwidth*(i*inheight/outheight);
frac = fracstep >> 1;
for (j=0 ; j<outwidth ; j+=4)
{
out[j] = inrow[frac>>16];
frac += fracstep;
out[j+1] = inrow[frac>>16];
frac += fracstep;
out[j+2] = inrow[frac>>16];
frac += fracstep;
out[j+3] = inrow[frac>>16];
frac += fracstep;
}
}
}
/*
================
GL_MipMap
Operates in place, quartering the size of the texture
================
*/
void GL_MipMap (byte *in, int width, int height)
{
int i, j;
byte *out;
width <<=2;
height >>= 1;
out = in;
for (i=0 ; i<height ; i++, in+=width)
{
for (j=0 ; j<width ; j+=8, out+=4, in+=8)
{
out[0] = (in[0] + in[4] + in[width+0] + in[width+4])>>2;
out[1] = (in[1] + in[5] + in[width+1] + in[width+5])>>2;
out[2] = (in[2] + in[6] + in[width+2] + in[width+6])>>2;
out[3] = (in[3] + in[7] + in[width+3] + in[width+7])>>2;
}
}
}
#ifdef SUPPORT_8BIT_MIPMAPGENERATION
/*
================
GL_MipMap8Bit
Mipping for 8 bit textures
The "in" and "out" arguments can point to the same buffer if desired
================
*/
void GL_MipMap8Bit (byte *in, byte* out, int width, int height)
{
int i, j;
unsigned short r,g,b;
byte *at1, *at2, *at3, *at4;
// width <<=2;
height >>= 1;
for (i=0 ; i<height ; i++, in+=width)
{
for (j=0 ; j<width ; j+=2, out+=1, in+=2)
{
at1 = (byte *) (d_8to24table + in[0]);
at2 = (byte *) (d_8to24table + in[1]);
at3 = (byte *) (d_8to24table + in[width+0]);
at4 = (byte *) (d_8to24table + in[width+1]);
r = (at1[0]+at2[0]+at3[0]+at4[0]); r>>=5;
g = (at1[1]+at2[1]+at3[1]+at4[1]); g>>=5;
b = (at1[2]+at2[2]+at3[2]+at4[2]); b>>=5;
out[0] = d_15to8table[(r<<0) + (g<<5) + (b<<10)];
}
}
}
#endif // SUPPORT_8BIT_MIPMAPGENERATION
void glTexImage2DHelper( GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const GLvoid *pixels )
{
// In full OpenGL The internalformat can be 1..4, to indicate how many components of the data are valid.
// OpenGL ES requires the internalformat argument match the format for glTexImage2D.
glTexImage2D(target, level, format, width, height, border, format, type, pixels);
}
// Uncomment to enable manual MipMap generation
#define USE_MANUAL_MIPMAP_GEN
// Uncomment one of the following:
// #define USE_16BPP_WITH_8888_ALPHA
// #define USE_16BPP_WITH_5551_ALPHA // <--- This has bugs on the simulator and the device. (Device has all alpha images invisible.)
#define USE_16BPP_WITH_4444_ALPHA // <--- This has bugs on the simulator, works in device
// #define USE_32BPP
// #define USE_32BPP_MANUAL_MIPMAP_GEN
#ifdef USE_MANUAL_MIPMAP_GEN
inline unsigned int average4(unsigned int a, unsigned int b,
unsigned int c, unsigned int d,
unsigned int shift, unsigned int mask) {
unsigned int aElem = (a >> shift) & mask;
unsigned int bElem = (b >> shift) & mask;
unsigned int cElem = (c >> shift) & mask;
unsigned int dElem = (d >> shift) & mask;
unsigned int avgElem = ((aElem + bElem + cElem + dElem) >> 2) & mask;
return avgElem << shift;
}
inline unsigned int average2(unsigned int a, unsigned int b,
unsigned int shift, unsigned int mask) {
unsigned int aElem = (a >> shift) & mask;
unsigned int bElem = (b >> shift) & mask;
unsigned int avgElem = ((aElem + bElem) >> 1) & mask;
return avgElem << shift;
}
inline unsigned int average4444(unsigned int a, unsigned int b) {
return
average2(a,b,0,0xf) |
average2(a,b,4,0xf) |
average2(a,b,8,0xf) |
average2(a,b,12,0xf);
}
inline unsigned int average565(unsigned int a, unsigned int b) {
return
average2(a,b,0,0x1f) |
average2(a,b,5,0x3f) |
average2(a,b,11,0x1f);
}
inline unsigned int average2_8888(unsigned int a, unsigned int b) {
return
average2(a,b,0,0xff) |
average2(a,b,8,0xff) |
average2(a,b,16,0xff) |
average2(a,b,24,0xff);
}
inline unsigned int average4_8888(unsigned int a, unsigned int b,
unsigned int c, unsigned int d) {
return
average4(a,b,c,d,0,0xff) |
average4(a,b,c,d,8,0xff) |
average4(a,b,c,d,16,0xff) |
average4(a,b,c,d,24,0xff);
}
#endif
// pData is 8 bpp 32-bit color
void sendTexture(int mipLevel, int width, int height, unsigned int* pData, qboolean alpha) {
if (alpha) {
#if defined(USE_16BPP_WITH_8888_ALPHA)
// 8888
glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#elif defined(USE_16BPP_WITH_5551_ALPHA)
// 5551
glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 0);
glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#else
// 4444
glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#endif
}
else {
#if 0
// 8888
glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#else
// 565
static unsigned short scaled[1024*512]; // [512*256];
glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
// Some OpenGL ES implementations do not have to be able to convert from GL_RGBA to GL_RGB format, so
// we must do it manually here:
unsigned char* pSrc = (unsigned char*) pData;
unsigned short* pDest = scaled;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
*pDest++ = ((pSrc[0] >> 3) << 11) |
((pSrc[1] >> 2) << 5) |
(pSrc[2] >> 3);
pSrc += 4;
}
}
glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, scaled);
#endif
}
}
/*
===============
GL_Upload32
===============
*/
void GL_Upload32 (unsigned *data, int width, int height, qboolean mipmap, qboolean alpha)
{
int samples;
int scaled_width, scaled_height;
static unsigned scaled[1024*512]; // [512*256];
for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
;
for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
;
scaled_width >>= (int)gl_picmip.value;
scaled_height >>= (int)gl_picmip.value;
if (scaled_width > gl_max_size.value)
scaled_width = (int) gl_max_size.value;
if (scaled_height > gl_max_size.value)
scaled_height = (int) gl_max_size.value;
if (scaled_width * scaled_height > (int) sizeof(scaled)/4)
Sys_Error ("GL_LoadTexture: too big");
samples = alpha ? gl_alpha_format : gl_solid_format;
texels += scaled_width * scaled_height;
if (scaled_width == width && scaled_height == height)
{
#if 0 // Disable this optimization, we want to be able to easily switch texture formats
if (!mipmap)
{
glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
goto done;
}
#endif
memcpy (scaled, data, width*height*4);
}
else
GL_ResampleTexture (data, width, height, scaled, scaled_width, scaled_height);
#if defined(USE_16BPP_WITH_8888_ALPHA) || defined(USE_16BPP_WITH_5551_ALPHA) || defined(USE_16BPP_WITH_4444_ALPHA)
// Upload as 16 bpp
#ifdef USE_MANUAL_MIPMAP_GEN
#else
// Use automatic MIPMAP generation
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
}
#endif
sendTexture(0, scaled_width, scaled_height, scaled, alpha);
#ifdef USE_MANUAL_MIPMAP_GEN
if (mipmap) {
// Compute mip levels
int mipWidth = scaled_width;
int mipHeight = scaled_height;
int mipLevel = 1;
while (mipWidth > 1 || mipHeight > 1) {
if (mipWidth > 1 && mipHeight > 1) {
// Scale horizontally and vertically
int srcWidth = mipWidth;
mipWidth >>= 1;
mipHeight >>= 1;
const unsigned int* pIn = (const unsigned int*) scaled;
unsigned int* pOut = (unsigned int*) scaled;
for(int y = 0; y < mipHeight; y++) {
for (int x = 0; x < mipWidth; x++) {
*pOut++ = average4_8888(pIn[0], pIn[1],
pIn[srcWidth], pIn[srcWidth+1]);
pIn += 2;
}
pIn += srcWidth;
}
}
else {
// Scale horizontally:
if (mipWidth > 1) {
mipWidth >>= 1;
const unsigned int* pIn = (const unsigned int*) scaled;
unsigned int* pOut = (unsigned int*) scaled;
unsigned int numTexels = mipHeight * mipWidth;
for(unsigned int i = 0; i < numTexels; i++) {
*pOut++ = average2_8888(pIn[0], pIn[1]);
pIn += 2;
}
}
// Scale vertically:
if (mipHeight > 1) {
mipHeight >>= 1;
const unsigned int* pIn = (const unsigned int*) scaled;
unsigned int* pOut = (unsigned int*) scaled;
for(int y = 0; y < mipHeight; y++) {
for (int x = 0; x < mipWidth; x++) {
*pOut++ = average2_8888(pIn[0], pIn[mipWidth]);
pIn += 1;
}
pIn += mipWidth;
}
}
}
sendTexture(mipLevel, mipWidth, mipHeight, scaled, alpha);
mipLevel++;
}
}
#else
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 0);
}
#endif
#elif defined(USE_32BPP)
// 8888
// Use automatic MIPMAP generation
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
}
glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 0);
}
#else
glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
if (mipmap)
{
int miplevel;
miplevel = 0;
while (scaled_width > 1 || scaled_height > 1)
{
GL_MipMap ((byte *)scaled, scaled_width, scaled_height);
scaled_width >>= 1;
scaled_height >>= 1;
if (scaled_width < 1)
scaled_width = 1;
if (scaled_height < 1)
scaled_height = 1;
miplevel++;
glTexImage2DHelper (GL_TEXTURE_2D, miplevel, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
}
}
#endif
done: ;
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
else
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
}
#ifdef USE_OPENGLES
void GL_Upload8_EXT (byte *data, int width, int height, qboolean mipmap, qboolean alpha)
{
int i, s, bytesUsed;
qboolean noalpha;
int p;
static unsigned j;
static unsigned char compressedTextureBuffer[1024*512]; // [512*256];
unsigned char* pTex = compressedTextureBuffer;
int scaled_width, scaled_height;
int miplevel = 0;
int originalScaledWidth;
int originalScaledHeight;
s = width*height;
// if there are no transparent pixels, make it a 3 component
// texture even if it was specified as otherwise
if (alpha)
{
noalpha = true;
for (i=0 ; i<s ; i++)
{
if (data[i] == 255)
noalpha = false;
}
if (alpha && noalpha)
alpha = false;
}
for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
;
for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
;
scaled_width >>= (int)gl_picmip.value;
scaled_height >>= (int)gl_picmip.value;
if (scaled_width > gl_max_size.value)
scaled_width = (int) gl_max_size.value;
if (scaled_height > gl_max_size.value)
scaled_height = (int) gl_max_size.value;
if (scaled_width * scaled_height > ((int) (sizeof(compressedTextureBuffer) * 3 / 4)))
Sys_Error ("GL_LoadTexture: too big");
// Copy the palette
int entrySize = alpha ? 4 : 3;
int paletteSize = entrySize * 256;
{
byte* pDest = compressedTextureBuffer;
const byte* pSrc = host_basepal;
if(alpha)
{
for(int i = 0; i< 255; i++)
{
*pDest++ = *pSrc++;
*pDest++ = *pSrc++;
*pDest++ = *pSrc++;
*pDest++ = 0xff;
}
// Entry 255 is transparent
*pDest++ = 0x00;
*pDest++ = 0x00;
*pDest++ = 0x00;
*pDest++ = 0x00;
}
else
{
memcpy(pDest, pSrc, paletteSize);
}
}
bytesUsed = paletteSize;
pTex += paletteSize;
texels += scaled_width * scaled_height;
if (scaled_width == width && scaled_height == height)
{
memcpy (pTex, data, scaled_width*scaled_height);
}
else
GL_Resample8BitTexture (data, width, height, pTex, scaled_width, scaled_height);
bytesUsed += scaled_width * scaled_height;
miplevel = 0;
originalScaledWidth = scaled_width;
originalScaledHeight = scaled_height;
if (mipmap)
{
#ifdef SUPPORT_8BIT_MIPMAPGENERATION
miplevel = 1;
while (scaled_width > 1 || scaled_height > 1)
{
byte* pDest = (byte*) pTex + scaled_width * scaled_height;
GL_MipMap8Bit ((byte *)pTex, pDest, scaled_width, scaled_height);
pTex = pDest;
scaled_width >>= 1;
scaled_height >>= 1;
if (scaled_width < 1)
scaled_width = 1;
if (scaled_height < 1)
scaled_height = 1;
bytesUsed += scaled_width * scaled_height;
miplevel++;
}
#else
Sys_Error("Unsupported attempt to generate 8 bit mip mapped texture. #define SUPPORT_8BIT_MIPMAPGENERATION");
#endif
}
GLint internalFormat = alpha ? GL_PALETTE8_RGBA8_OES : GL_PALETTE8_RGB8_OES;
glCompressedTexImage2D (GL_TEXTURE_2D, -miplevel, internalFormat,
originalScaledWidth, originalScaledHeight,
0, bytesUsed, compressedTextureBuffer);
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
else
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
}
#else
void GL_Upload8_EXT (byte *data, int width, int height, qboolean mipmap, qboolean alpha)
{
int i, s;
qboolean noalpha;
int p;
static unsigned j;
int samples;
static unsigned char scaled[1024*512]; // [512*256];
int scaled_width, scaled_height;
s = width*height;
// if there are no transparent pixels, make it a 3 component
// texture even if it was specified as otherwise
if (alpha)
{
noalpha = true;
for (i=0 ; i<s ; i++)
{
if (data[i] == 255)
noalpha = false;
}
if (alpha && noalpha)
alpha = false;
}
for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
;
for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
;
scaled_width >>= (int)gl_picmip.value;
scaled_height >>= (int)gl_picmip.value;
if (scaled_width > gl_max_size.value)
scaled_width = gl_max_size.value;
if (scaled_height > gl_max_size.value)
scaled_height = gl_max_size.value;
if (scaled_width * scaled_height > (int) sizeof(scaled))
Sys_Error ("GL_LoadTexture: too big");
samples = 1; // alpha ? gl_alpha_format : gl_solid_format;
texels += scaled_width * scaled_height;
if (scaled_width == width && scaled_height == height)
{
if (!mipmap)
{
glTexImage2D (GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, scaled_width, scaled_height, 0, GL_COLOR_INDEX , GL_UNSIGNED_BYTE, data);
goto done;
}
memcpy (scaled, data, width*height);
}
else
GL_Resample8BitTexture (data, width, height, scaled, scaled_width, scaled_height);
glCompressedTexImage2D (GL_TEXTURE_2D, 0, GL_PALETTE8_RGB8_OES, scaled_width, scaled_height, 0, s, scaled);
if (mipmap)
{
#ifdef SUPPORT_8BIT_MIPMAPGENERATION
int miplevel;
miplevel = 0;
while (scaled_width > 1 || scaled_height > 1)
{
GL_MipMap8Bit ((byte *)scaled, (byte*) scaled, scaled_width, scaled_height);
scaled_width >>= 1;
scaled_height >>= 1;
if (scaled_width < 1)
scaled_width = 1;
if (scaled_height < 1)
scaled_height = 1;
miplevel++;
glTexImage2D (GL_TEXTURE_2D, miplevel, GL_COLOR_INDEX8_EXT, scaled_width, scaled_height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, scaled);
}
#else
Sys_Error("Unsupported attept to generate 8 bit mip mapped texture.");
#endif
}
done: ;
if (mipmap)
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
else
{
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
}
}
#endif // ! OPENGL_ES
/*
===============
GL_Upload8
===============
*/
void GL_Upload8 (byte *data, int width, int height, qboolean mipmap, qboolean alpha)
{
static unsigned trans[640*480]; // FIXME, temporary
int i, s;
qboolean noalpha;
int p;
s = width*height;
// if there are no transparent pixels, make it a 3 component
// texture even if it was specified as otherwise
if (alpha)
{
noalpha = true;
for (i=0 ; i<s ; i++)
{
p = data[i];
if (p == 255)
noalpha = false;
trans[i] = d_8to24table[p];
}
if (alpha && noalpha)
alpha = false;
}
else
{
if (s&3)
Sys_Error ("GL_Upload8: s&3");
for (i=0 ; i<s ; i+=4)
{
trans[i] = d_8to24table[data[i]];
trans[i+1] = d_8to24table[data[i+1]];
trans[i+2] = d_8to24table[data[i+2]];
trans[i+3] = d_8to24table[data[i+3]];
}
}
if (VID_Is8bit() && (data!=scrap_texels[0])
#if !defined(USE_OPENGLES)
&& !alpha
#endif
) {
GL_Upload8_EXT (data, width, height, mipmap, alpha);
return;
}
GL_Upload32 (trans, width, height, mipmap, alpha);
}
/*
================
GL_LoadTexture
================
*/
int GL_LoadTexture (const char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha)
{
qboolean noalpha;
int i, p, s;
gltexture_t *glt;
// see if the texture is allready present
if (identifier[0])
{
for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
{
if (!strcmp (identifier, glt->identifier))
{
if (width != glt->width || height != glt->height)
Sys_Error ("GL_LoadTexture: cache mismatch");
return gltextures[i].texnum;
}
}
#ifdef USE_OPENGLES
// Surely we want to remember this new texture.
// Doing this costs 1% fps per timedemo on a DX7 PC,
// probably because of the linear search through the
// texture cache, but it saves 10 MB of VM growth per
// level load. It also makes the GL_TEXTUREMODE
// console command work correctly.
numgltextures++;
#endif
}
else {
glt = &gltextures[numgltextures];
numgltextures++;
}
strcpy (glt->identifier, identifier);
glt->texnum = texture_extension_number;
glt->width = width;
glt->height = height;
glt->mipmap = mipmap;
GL_Bind(texture_extension_number);
#ifdef USE_TEXTURE_STORE
textureStore::get()->create(width, height, data, mipmap, alpha);
#else
GL_Upload8 (data, width, height, mipmap, alpha);
#endif
texture_extension_number++;
return texture_extension_number-1;
}
/****************************************/
static GLenum oldtarget = TEXTURE0_SGIS;
void GL_SelectTexture (GLenum target)
{
if (!gl_mtexable)
return;
#ifdef USE_OPENGLES
glActiveTexture(target);
#else
qglSelectTextureSGIS(target);
#endif
if (target == oldtarget)
return;
cnttextures[oldtarget-TEXTURE0_SGIS] = currenttexture;
currenttexture = cnttextures[target-TEXTURE0_SGIS];
oldtarget = target;
}
// OpenGL ES compatible DrawQuad utility
#define BEGIN_QUAD glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
#define END_QUAD glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
void DrawQuad_NoTex(float x, float y, float w, float h)
{
BEGIN_QUAD
float vertex[2*4] = {x,y,x+w,y, x+w, y+h, x, y+h};
short index[4] = {0, 1, 2, 3};
glVertexPointer( 2, GL_FLOAT, 0, vertex);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, index);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
END_QUAD
}
void DrawQuad(float x, float y, float w, float h, float u, float v, float uw, float vh)
{
BEGIN_QUAD
float texcoord[2*4] = {u, v, u + uw, v, u + uw, v + vh, u, v + vh};
float vertex[2*4] = {x,y,x+w,y, x+w, y+h, x, y+h};
unsigned short index[4] = {0, 1, 2, 3};
glTexCoordPointer( 2, GL_FLOAT, 0, texcoord);
glVertexPointer( 2, GL_FLOAT, 0, vertex);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, index);
END_QUAD
}
#ifdef USE_OPENGLES
// Reimplementation of OpenGL functions that are missing in OpenGL ES
void glColor3f(GLfloat r, GLfloat g, GLfloat b)
{
glColor4f(r, g, b, 1.0f);
}
void glColor4fv(GLfloat* pColor)
{
glColor4f(pColor[0], pColor[1], pColor[2], pColor[3]);
}
float gVertexBuffer[VERTEXARRAYSIZE];
float gColorBuffer[VERTEXARRAYSIZE];
float gTexCoordBuffer[VERTEXARRAYSIZE];
// Called when we've lost the OpenGL context and have to recreate it.
extern void GL_Init();
extern void R_InitParticleTexture2();
extern void GL_UploadLightmaps();
extern void R_ReloadSky();
void GL_ReInit() {
GL_Init();
textureStore::get()->rebindAll();
scrap_dirty = true;
R_InitParticleTexture2();
GL_UploadLightmaps();
R_ReloadSky();
}
#endif
#ifdef DEBUG_OPENGL_CALLS
void checkGLImp(const char* state, const char* file, int line) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
Sys_Error("%s: error 0x%04X at %s:%d\n", state, error, file, line);
}
}
#endif