quakeforge/libs/video/renderer/glsl/glsl_textures.c

536 lines
13 KiB
C

/*
glsl_textures.c
Texture format setup.
Copyright (C) 1996-1997 Id Software, Inc.
Copyright (C) 2001 Ragnvald Maartmann-Moe IV
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define NH_DEFINE
#include "namehack.h"
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include "QF/cmd.h"
#include "QF/mathlib.h"
#include "QF/model.h"
#include "QF/sys.h"
#include "QF/vrect.h"
#include "QF/GLSL/defines.h"
#include "QF/GLSL/funcs.h"
#include "QF/GLSL/qf_textures.h"
struct scrap_s {
GLuint tnum;
int size; // in pixels, for now, always square, power of 2
int format;
int bpp;
byte *data; // local copy of the texture so updates can be batched
vrect_t *batch;
vrect_t *free_rects;
vrect_t *rects;
subpic_t *subpics;
struct scrap_s *next;
};
static scrap_t *scrap_list;
static int max_tex_size;
int
GLSL_LoadQuakeTexture (const char *identifier, int width, int height,
byte *data)
{
GLuint tnum;
qfeglGenTextures (1, &tnum);
qfeglBindTexture (GL_TEXTURE_2D, tnum);
qfeglTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE,
width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
qfeglGenerateMipmap (GL_TEXTURE_2D);
return tnum;
}
static void
GLSL_Resample8BitTexture (unsigned char *in, int inwidth, int inheight,
unsigned char *out, int outwidth, int outheight)
{
// Improvements here should be mirrored in build_skin_8 in gl_skin.c
unsigned char *inrow;
int i, j;
unsigned int frac, fracstep;
if (!outwidth || !outheight)
return;
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 ++) {
out[j] = inrow[frac >> 16];
frac += fracstep;
}
}
}
static void
GLSL_Mipmap8BitTexture (const byte *src, unsigned width, unsigned height,
byte *mip)
{
unsigned mw = width >> 1;
unsigned mh = height >> 1;
unsigned i, j;
mw = max (mw, 1);
mh = max (mh, 1);
for (j = 0; j < mh; j++) {
for (i = 0; i < mw; i++) {
*mip++ = src [j * 2 * width + i * 2];
}
}
}
int
GLSL_LoadQuakeMipTex (const texture_t *tex)
{
unsigned swidth, sheight;
GLuint tnum;
byte *data = (byte *) tex;
byte *buffer = 0;
byte *scaled;
int lod;
for (swidth = 1; swidth < tex->width; swidth <<= 1);
for (sheight = 1; sheight < tex->height; sheight <<= 1);
qfeglGenTextures (1, &tnum);
qfeglBindTexture (GL_TEXTURE_2D, tnum);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST_MIPMAP_NEAREST);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (swidth != tex->width || sheight != tex->height)
buffer = malloc (swidth * sheight);
// preshift so swidth and sheight are the correct sizes end the end of
// the loop
swidth <<= 1;
sheight <<= 1;
for (lod = 0; lod < MIPLEVELS; lod++) {
// preshift so swidth and sheight are the correct sizes end the end of
// the loop
swidth >>= 1;
sheight >>= 1;
swidth = max (swidth, 1);
sheight = max (sheight, 1);
if (buffer) {
unsigned w = tex->width;
unsigned h = tex->height;
w = max (w >> lod, 1);
h = max (h >> lod, 1);
GLSL_Resample8BitTexture (data + tex->offsets[lod], w, h,
buffer, swidth, sheight);
scaled = buffer;
} else {
scaled = data + tex->offsets[lod];
}
qfeglTexImage2D (GL_TEXTURE_2D, lod, GL_LUMINANCE, swidth, sheight,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, scaled);
}
if (swidth > 1 || sheight > 1) {
// mipmap will hold the reduced image, so this is more than enough
byte *mipmap = malloc (swidth * sheight);
byte *mip = mipmap;
while (swidth > 1 || sheight > 1) {
// scaled holds the source of the last lod uploaded
GLSL_Mipmap8BitTexture (scaled, swidth, sheight, mip);
swidth >>= 1;
sheight >>= 1;
swidth = max (swidth, 1);
sheight = max (sheight, 1);
qfeglTexImage2D (GL_TEXTURE_2D, lod, GL_LUMINANCE, swidth, sheight,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, mip);
scaled = mip;
mip += swidth * sheight;
lod++;
}
free (mipmap);
}
if (buffer)
free (buffer);
return tnum;
}
int
GLSL_LoadRGBTexture (const char *identifier, int width, int height, byte *data)
{
GLuint tnum;
qfeglGenTextures (1, &tnum);
qfeglBindTexture (GL_TEXTURE_2D, tnum);
qfeglTexImage2D (GL_TEXTURE_2D, 0, GL_RGB,
width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
qfeglGenerateMipmap (GL_TEXTURE_2D);
return tnum;
}
int
GLSL_LoadRGBATexture (const char *identifier, int width, int height, byte *data)
{
GLuint tnum;
qfeglGenTextures (1, &tnum);
qfeglBindTexture (GL_TEXTURE_2D, tnum);
qfeglTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
qfeglGenerateMipmap (GL_TEXTURE_2D);
return tnum;
}
void
GLSL_ReleaseTexture (int tex)
{
GLuint tnum = tex;
qfeglDeleteTextures (1, &tnum);
}
static void
glsl_scraps_f (void)
{
scrap_t *scrap;
vrect_t *rect;
int area;
if (!scrap_list) {
Sys_Printf ("No scraps\n");
return;
}
for (scrap = scrap_list; scrap; scrap = scrap->next) {
for (rect = scrap->free_rects, area = 0; rect; rect = rect->next)
area += rect->width * rect->height;
Sys_Printf ("tnum=%u size=%d format=%04x bpp=%d free=%d%%\n",
scrap->tnum, scrap->size, scrap->format, scrap->bpp,
area * 100 / (scrap->size * scrap->size));
if (Cmd_Argc () > 1) {
for (rect = scrap->rects, area = 0; rect; rect = rect->next)
Sys_Printf ("%d %d %d %d\n", rect->x, rect->y,
rect->width, rect->height);
}
}
}
void
GLSL_TextureInit (void)
{
qfeglGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_tex_size);
Sys_MaskPrintf (SYS_GLSL, "max texture size: %d\n", max_tex_size);
Cmd_AddCommand ("glsl_scraps", glsl_scraps_f, "Dump GLSL scrap stats");
}
scrap_t *
GLSL_CreateScrap (int size, int format, int linear)
{
int i;
int bpp;
scrap_t *scrap;
for (i = 0; i < 16; i++)
if (size <= 1 << i)
break;
size = 1 << i;
size = min (size, max_tex_size);
switch (format) {
case GL_ALPHA:
case GL_LUMINANCE:
bpp = 1;
break;
case GL_LUMINANCE_ALPHA:
bpp = 2;
break;
case GL_RGB:
bpp = 3;
break;
case GL_RGBA:
bpp = 4;
break;
default:
Sys_Error ("GL_CreateScrap: Invalid texture format");
}
scrap = malloc (sizeof (scrap_t));
qfeglGenTextures (1, &scrap->tnum);
scrap->size = size;
scrap->format = format;
scrap->bpp = bpp;
scrap->free_rects = VRect_New (0, 0, size, size);
scrap->rects = 0;
scrap->subpics = 0;
scrap->next = scrap_list;
scrap_list = scrap;
scrap->data = calloc (1, size * size * bpp);
scrap->batch = 0;
qfeglBindTexture (GL_TEXTURE_2D, scrap->tnum);
qfeglTexImage2D (GL_TEXTURE_2D, 0, format,
size, size, 0, format, GL_UNSIGNED_BYTE, scrap->data);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (linear) {
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
qfeglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
qfeglGenerateMipmap (GL_TEXTURE_2D);
return scrap;
}
void
GLSL_ScrapClear (scrap_t *scrap)
{
vrect_t *t;
subpic_t *sp;
while (scrap->free_rects) {
t = scrap->free_rects;
scrap->free_rects = t->next;
VRect_Delete (t);
}
while (scrap->rects) {
t = scrap->rects;
scrap->rects = t->next;
VRect_Delete (t);
}
while (scrap->subpics) {
sp = scrap->subpics;
scrap->subpics = (subpic_t *) sp->next;
free (sp);
}
scrap->free_rects = VRect_New (0, 0, scrap->size, scrap->size);
}
void
GLSL_DestroyScrap (scrap_t *scrap)
{
scrap_t **s;
for (s = &scrap_list; *s; s = &(*s)->next) {
if (*s == scrap) {
*s = scrap->next;
break;
}
}
GLSL_ScrapClear (scrap);
VRect_Delete (scrap->free_rects);
GLSL_ReleaseTexture (scrap->tnum);
free (scrap->data);
free (scrap);
}
int
GLSL_ScrapTexture (scrap_t *scrap)
{
return scrap->tnum;
}
subpic_t *
GLSL_ScrapSubpic (scrap_t *scrap, int width, int height)
{
int i, w, h;
vrect_t **t, **best;
vrect_t *old, *frags, *rect;
subpic_t *subpic;
for (i = 0; i < 16; i++)
if (width <= (1 << i))
break;
w = 1 << i;
for (i = 0; i < 16; i++)
if (height <= (1 << i))
break;
h = 1 << i;
best = 0;
for (t = &scrap->free_rects; *t; t = &(*t)->next) {
if ((*t)->width < w || (*t)->height < h)
continue; // won't fit
if (!best) {
best = t;
continue;
}
if ((*t)->width <= (*best)->width || (*t)->height <= (*best)->height)
best = t;
}
if (!best)
return 0; // couldn't find a spot
old = *best;
*best = old->next;
rect = VRect_New (old->x, old->y, w, h);
frags = VRect_Difference (old, rect);
VRect_Delete (old);
if (frags) {
// old was bigger than the requested size
for (old = frags; old->next; old = old->next)
;
old->next = scrap->free_rects;
scrap->free_rects = frags;
}
rect->next = scrap->rects;
scrap->rects = rect;
subpic = malloc (sizeof (subpic_t));
*((subpic_t **) &subpic->next) = scrap->subpics;
scrap->subpics = subpic;
*((scrap_t **) &subpic->scrap) = scrap;
*((vrect_t **) &subpic->rect) = rect;
*((int *) &subpic->tnum) = scrap->tnum;
*((int *) &subpic->width) = width;
*((int *) &subpic->height) = height;
*((float *) &subpic->size) = 1.0 / scrap->size;
return subpic;
}
void
GLSL_SubpicDelete (subpic_t *subpic)
{
scrap_t *scrap = (scrap_t *) subpic->scrap;
vrect_t *rect = (vrect_t *) subpic->rect;
vrect_t *old, *merge;
vrect_t **t;
subpic_t **sp;
for (sp = &scrap->subpics; *sp; sp = (subpic_t **) &(*sp)->next)
if (*sp == subpic)
break;
if (*sp != subpic)
Sys_Error ("GLSL_ScrapDelSubpic: broken subpic");
*sp = (subpic_t *) subpic->next;
free (subpic);
for (t = &scrap->rects; *t; t = &(*t)->next)
if (*t == rect)
break;
if (*t != rect)
Sys_Error ("GLSL_ScrapDelSubpic: broken subpic");
*t = rect->next;
do {
merge = 0;
for (t = &scrap->free_rects; *t; t = &(*t)->next) {
merge = VRect_Merge (*t, rect);
if (merge) {
old = *t;
*t = (*t)->next;
VRect_Delete (old);
VRect_Delete (rect);
rect = merge;
break;
}
}
} while (merge);
rect->next = scrap->free_rects;
scrap->free_rects = rect;
}
void
GLSL_SubpicUpdate (subpic_t *subpic, byte *data, int batch)
{
scrap_t *scrap = (scrap_t *) subpic->scrap;
vrect_t *rect = (vrect_t *) subpic->rect;
byte *dest;
int step, sbytes;
int i;
if (batch) {
if (scrap->batch) {
vrect_t *r = scrap->batch;
scrap->batch = VRect_Union (r, rect);
VRect_Delete (r);
} else {
scrap->batch = VRect_New (rect->x, rect->y,
rect->width, rect->height);
}
step = scrap->size * scrap->bpp;
sbytes = subpic->width * scrap->bpp;
dest = scrap->data + rect->y * step + rect->x * scrap->bpp;
for (i = 0; i < subpic->height; i++, dest += step, data += sbytes)
memcpy (dest, data, sbytes);
} else {
qfeglBindTexture (GL_TEXTURE_2D, scrap->tnum);
qfeglTexSubImage2D (GL_TEXTURE_2D, 0, rect->x, rect->y,
subpic->width, subpic->height, scrap->format,
GL_UNSIGNED_BYTE, data);
}
}
void
GLSL_ScrapFlush (scrap_t *scrap)
{
vrect_t *rect = scrap->batch;;
if (!rect)
return;
//FIXME: it seems gl (as opposed to egl) allows row step to be specified.
//should update to not update the entire horizontal block
qfeglBindTexture (GL_TEXTURE_2D, scrap->tnum);
qfeglTexSubImage2D (GL_TEXTURE_2D, 0, 0, rect->y,
scrap->size, rect->height, scrap->format,
GL_UNSIGNED_BYTE,
scrap->data + rect->y * scrap->size * scrap->bpp);
VRect_Delete (rect);
scrap->batch = 0;
}