Permalink
Fetching contributors…
Cannot retrieve contributors at this time
916 lines (770 sloc) 23 KB
/*
* See Licensing and Copyright notice in naev.h
*/
/**
* @file opengl_tex.c
*
* @brief This file handles the opengl texture wrapper routines.
*/
#include "opengl.h"
#include "naev.h"
#include <stdlib.h>
#include <stdio.h>
#include "nstring.h"
#include "log.h"
#include "ndata.h"
#include "nfile.h"
#include "gui.h"
#include "conf.h"
#include "npng.h"
#include "md5.h"
/*
* graphic list
*/
/**
* @brief Represents a node in the texture list.
*/
typedef struct glTexList_ {
struct glTexList_ *next; /**< Next in linked list */
glTexture *tex; /**< associated texture */
int used; /**< counts how many times texture is being used */
} glTexList;
static glTexList* texture_list = NULL; /**< Texture list. */
/*
* Extensions.
*/
static int gl_tex_ext_npot = 0; /**< Support for GL_ARB_texture_non_power_of_two. */
/*
* prototypes
*/
/* misc */
/*static int SDL_VFlipSurface( SDL_Surface* surface );*/
static int SDL_IsTrans( SDL_Surface* s, int x, int y );
static uint8_t* SDL_MapTrans( SDL_Surface* s, int w, int h );
static size_t gl_transSize( const int w, const int h );
/* glTexture */
static GLuint gl_loadSurface( SDL_Surface* surface, int *rw, int *rh, unsigned int flags, int freesur );
static glTexture* gl_loadNewImage( const char* path, unsigned int flags );
/* List. */
static glTexture* gl_texExists( const char* path );
static int gl_texAdd( glTexture *tex );
/**
* @brief Gets the closest power of two.
* @param n Number to get closest power of two to.
* @return Closest power of two to the number.
*/
int gl_pot( int n )
{
int i = 1;
while (i < n)
i <<= 1;
return i;
}
#if 0
/**
* @brief Flips the surface vertically.
*
* @param surface Surface to flip.
* @return 0 on success.
*/
static int SDL_VFlipSurface( SDL_Surface* surface )
{
/* flip the image */
Uint8 *rowhi, *rowlo, *tmpbuf;
int y;
tmpbuf = malloc(surface->pitch);
if ( tmpbuf == NULL ) {
WARN("Out of memory");
return -1;
}
rowhi = (Uint8 *)surface->pixels;
rowlo = rowhi + (surface->h * surface->pitch) - surface->pitch;
for (y = 0; y < surface->h / 2; ++y ) {
memcpy(tmpbuf, rowhi, surface->pitch);
memcpy(rowhi, rowlo, surface->pitch);
memcpy(rowlo, tmpbuf, surface->pitch);
rowhi += surface->pitch;
rowlo -= surface->pitch;
}
free(tmpbuf);
/* flipping done */
return 0;
}
#endif
/**
* @brief Checks to see if a position of the surface is transparent.
*
* @param s Surface to check for transparency.
* @param x X position of the pixel to check.
* @param y Y position of the pixel to check.
* @return 0 if the pixel isn't transparent, 0 if it is.
*/
static int SDL_IsTrans( SDL_Surface* s, int x, int y )
{
int bpp;
Uint8 *p;
Uint32 pixelcolour;
bpp = s->format->BytesPerPixel;
/* here p is the address to the pixel we want to retrieve */
p = (Uint8 *)s->pixels + y*s->pitch + x*bpp;
pixelcolour = 0;
switch(bpp) {
case 1:
pixelcolour = *p;
break;
case 2:
pixelcolour = *(Uint16 *)p;
break;
case 3:
#if HAS_BIGENDIAN
pixelcolour = p[0] << 16 | p[1] << 8 | p[2];
#else /* HAS_BIGENDIAN */
pixelcolour = p[0] | p[1] << 8 | p[2] << 16;
#endif /* HAS_BIGENDIAN */
break;
case 4:
pixelcolour = *(Uint32 *)p;
break;
}
/* test whether pixels colour == colour of transparent pixels for that surface */
return ((pixelcolour & s->format->Amask) < (Uint32)(0.1*(double)s->format->Amask));
}
/**
* @brief Maps the surface transparency.
*
* Basically generates a map of what pixels are transparent. Good for pixel
* perfect collision routines.
*
* @param s Surface to map it's transparency.
* @param w Width to map.
* @param h Height to map.
* @return 0 on success.
*/
static uint8_t* SDL_MapTrans( SDL_Surface* s, int w, int h )
{
int i,j;
size_t size;
uint8_t *t;
/* Get limit.s */
if (w < 0)
w = s->w;
if (h < 0)
h = s->h;
/* alloc memory for just enough bits to hold all the data we need */
size = gl_transSize(w, h);
t = malloc(size);
if (t==NULL) {
WARN("Out of Memory");
return NULL;
}
memset(t, 0, size); /* important, must be set to zero */
/* Check each pixel individually. */
for (i=0; i<h; i++)
for (j=0; j<w; j++) /* sets each bit to be 1 if not transparent or 0 if is */
t[(i*w+j)/8] |= (SDL_IsTrans(s,j,i)) ? 0 : (1<<((i*w+j)%8));
return t;
}
/*
* @brief Gets the size needed for a transparency map.
*
* @param w Width of the image.
* @param h Height of the image.
* @return The size in bytes.
*/
static size_t gl_transSize( const int w, const int h )
{
/* One bit per pixel, plus remainder. */
return w*h/8 + ((w*h%8)?1:0);
}
/**
* @brief Prepares the surface to be loaded as a texture.
*
* @param surface to load that is freed in the process.
* @return New surface that is prepared for texture loading.
*/
SDL_Surface* gl_prepareSurface( SDL_Surface* surface )
{
SDL_Surface* temp;
int potw, poth;
SDL_Rect rtemp;
#if ! SDL_VERSION_ATLEAST(1,3,0)
Uint32 saved_flags;
#endif /* ! SDL_VERSION_ATLEAST(1,3,0) */
/* Make size power of two. */
potw = gl_pot(surface->w);
poth = gl_pot(surface->h);
if (gl_needPOT() && ((potw != surface->w) || (poth != surface->h))) {
/* we must blit with an SDL_Rect */
rtemp.x = rtemp.y = 0;
rtemp.w = surface->w;
rtemp.h = surface->h;
/* saves alpha */
#if SDL_VERSION_ATLEAST(1,3,0)
SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
/* create the temp POT surface */
temp = SDL_CreateRGBSurface( 0, potw, poth,
surface->format->BytesPerPixel*8, RGBAMASK );
#else /* SDL_VERSION_ATLEAST(1,3,0) */
saved_flags = surface->flags & (SDL_SRCALPHA | SDL_RLEACCELOK);
if ((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA) {
SDL_SetAlpha( surface, 0, SDL_ALPHA_OPAQUE );
SDL_SetColorKey( surface, 0, surface->format->colorkey );
}
/* create the temp POT surface */
temp = SDL_CreateRGBSurface( SDL_SRCCOLORKEY,
potw, poth, surface->format->BytesPerPixel*8, RGBAMASK );
#endif /* SDL_VERSION_ATLEAST(1,3,0) */
if (temp == NULL) {
WARN("Unable to create POT surface: %s", SDL_GetError());
return 0;
}
if (SDL_FillRect( temp, NULL,
SDL_MapRGBA(surface->format,0,0,0,SDL_ALPHA_TRANSPARENT))) {
WARN("Unable to fill rect: %s", SDL_GetError());
return 0;
}
/* change the surface to the new blitted one */
SDL_BlitSurface( surface, &rtemp, temp, &rtemp);
SDL_FreeSurface( surface );
surface = temp;
#if ! SDL_VERSION_ATLEAST(1,3,0)
/* set saved alpha */
if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA )
SDL_SetAlpha( surface, 0, 0 );
#endif /* ! SDL_VERSION_ATLEAST(1,3,0) */
}
return surface;
}
/**
* @brief Checks to see if mipmaps are supported and enabled.
*
* @return 1 if mipmaps are supported and enabled.
*/
int gl_texHasMipmaps (void)
{
return (nglGenerateMipmap != NULL);
}
/**
* @brief Checks to see if texture compression is available and enabled.
*
* @return 1 if texture compression is available and enabled.
*/
int gl_texHasCompress (void)
{
return (nglCompressedTexImage2D != NULL);
}
/**
* @brief Loads a surface into an opengl texture.
*
* @param surface Surface to load into a texture.
* @param flags Flags to use.
* @param[out] rw Real width of the texture.
* @param[out] rh Real height of the texture.
* @return The opengl texture id.
*/
static GLuint gl_loadSurface( SDL_Surface* surface, int *rw, int *rh, unsigned int flags, int freesur )
{
GLuint texture;
GLfloat param;
/* Prepare the surface. */
surface = gl_prepareSurface( surface );
if (rw != NULL)
(*rw) = surface->w;
if (rh != NULL)
(*rh) = surface->h;
/* opengl texture binding */
glGenTextures( 1, &texture ); /* Creates the texture */
glBindTexture( GL_TEXTURE_2D, texture ); /* Loads the texture */
/* Filtering, LINEAR is better for scaling, nearest looks nicer, LINEAR
* also seems to create a bit of artifacts around the edges */
if ((gl_screen.scale != 1.) || (flags & OPENGL_TEX_MIPMAPS)) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
/* Always wrap just in case. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
/* now lead the texture data up */
SDL_LockSurface( surface );
if (gl_texHasCompress()) {
glTexImage2D( GL_TEXTURE_2D, 0, surface->format->BytesPerPixel,
surface->w, surface->h, 0, GL_COMPRESSED_RGBA,
GL_UNSIGNED_BYTE, surface->pixels );
}
else {
glTexImage2D( GL_TEXTURE_2D, 0, surface->format->BytesPerPixel,
surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels );
}
SDL_UnlockSurface( surface );
/* Create mipmaps. */
if ((flags & OPENGL_TEX_MIPMAPS) && gl_texHasMipmaps()) {
/* Do fancy stuff. */
if (gl_hasExt("GL_EXT_texture_filter_anisotropic")) {
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &param);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, param);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 9);
/* Now generate the mipmaps. */
nglGenerateMipmap(GL_TEXTURE_2D);
}
/* cleanup */
if (freesur)
SDL_FreeSurface( surface );
gl_checkErr();
return texture;
}
/**
* @brief Wrapper for gl_loadImagePad that includes transparency mapping.
*
* @param name Name to load with.
* @param surface Surface to load.
* @param rw RWops containing data to hash.
* @param flags Flags to use.
* @param w Non-padded width.
* @param h Non-padded height.
* @param sx X sprites.
* @param sy Y sprites.
* @param freesur Whether or not to free the surface.
* @return The glTexture for surface.
*/
glTexture* gl_loadImagePadTrans( const char *name, SDL_Surface* surface, SDL_RWops *rw,
unsigned int flags, int w, int h, int sx, int sy, int freesur )
{
glTexture *texture;
int i, filesize;
size_t cachesize, pngsize;
uint8_t *trans;
char *cachefile, *data;
char digest[33];
md5_state_t md5;
md5_byte_t *md5val;
if (name != NULL) {
texture = gl_texExists( name );
if (texture != NULL)
return texture;
}
if (flags & OPENGL_TEX_MAPTRANS)
flags ^= OPENGL_TEX_MAPTRANS;
/* Appropriate size for the transparency map, see SDL_MapTrans */
cachesize = gl_transSize(w, h);
cachefile = NULL;
trans = NULL;
if (rw != NULL) {
md5val = malloc(16);
md5_init(&md5);
pngsize = SDL_RWseek( rw, 0, SEEK_END );
SDL_RWseek( rw, 0, SEEK_SET );
data = malloc(pngsize);
if (data == NULL)
WARN("Out of memory!");
else {
SDL_RWread( rw, data, pngsize, 1 );
md5_append( &md5, (md5_byte_t*)data, pngsize );
free(data);
}
md5_finish( &md5, md5val );
for (i=0; i<16; i++)
nsnprintf( &digest[i * 2], 3, "%02x", md5val[i] );
free(md5val);
cachefile = malloc( PATH_MAX );
nsnprintf( cachefile, PATH_MAX, "%scollisions/%s",
nfile_cachePath(), digest );
/* Attempt to find a cached transparency map. */
if (nfile_fileExists(cachefile)) {
trans = (uint8_t*)nfile_readFile( &filesize, cachefile );
/* Consider cached data invalid if the length doesn't match. */
if (trans != NULL && cachesize != (unsigned int)filesize) {
free(trans);
trans = NULL;
}
/* Cached data matches, no need to overwrite. */
else {
free(cachefile);
cachefile = NULL;
}
}
}
else {
/* We could hash raw pixel data here, but that's slower than just
* generating the map from scratch.
*/
WARN("Texture '%s' has no RWops", name);
}
if (trans == NULL) {
SDL_LockSurface(surface);
trans = SDL_MapTrans( surface, w, h );
SDL_UnlockSurface(surface);
if (cachefile != NULL) {
/* Cache newly-generated transparency map. */
nfile_dirMakeExist( "%s/collisions/", nfile_cachePath() );
nfile_writeFile( (char*)trans, cachesize, cachefile );
free(cachefile);
}
}
texture = gl_loadImagePad( name, surface, flags, w, h, sx, sy, freesur );
texture->trans = trans;
return texture;
}
/**
* @brief Loads the already padded SDL_Surface to a glTexture.
*
* @param name Name to load with.
* @param surface Surface to load.
* @param flags Flags to use.
* @param w Non-padded width.
* @param h Non-padded height.
* @param sx X sprites.
* @param sy Y sprites.
* @param freesur Whether or not to free the surface.
* @return The glTexture for surface.
*/
glTexture* gl_loadImagePad( const char *name, SDL_Surface* surface,
unsigned int flags, int w, int h, int sx, int sy, int freesur )
{
glTexture *texture;
int rw, rh;
/* Make sure doesn't already exist. */
if (name != NULL) {
texture = gl_texExists( name );
if (texture != NULL)
return texture;
}
if (flags & OPENGL_TEX_MAPTRANS)
return gl_loadImagePadTrans( name, surface, NULL, flags, w, h,
sx, sy, freesur );
/* set up the texture defaults */
texture = calloc( 1, sizeof(glTexture) );
texture->w = (double) w;
texture->h = (double) h;
texture->sx = (double) sx;
texture->sy = (double) sy;
texture->texture = gl_loadSurface( surface, &rw, &rh, flags, freesur );
texture->rw = (double) rw;
texture->rh = (double) rh;
texture->sw = texture->w / texture->sx;
texture->sh = texture->h / texture->sy;
texture->srw = texture->sw / texture->rw;
texture->srh = texture->sh / texture->rh;
if (name != NULL) {
texture->name = strdup(name);
gl_texAdd( texture );
}
else
texture->name = NULL;
return texture;
}
/**
* @brief Loads the SDL_Surface to a glTexture.
*
* @param surface Surface to load.
* @param flags Flags to use.
* @return The glTexture for surface.
*/
glTexture* gl_loadImage( SDL_Surface* surface, unsigned int flags )
{
return gl_loadImagePad( NULL, surface, flags, surface->w, surface->h, 1, 1, 1 );
}
/**
* @brief Check to see if a texture matching a path already exists.
*
* @param path Path to the texture.
* @return The texture, or NULL if none was found.
*/
static glTexture* gl_texExists( const char* path )
{
glTexList *cur;
/* check to see if it already exists */
if (texture_list != NULL) {
for (cur=texture_list; cur!=NULL; cur=cur->next) {
if (strcmp(path,cur->tex->name)==0) {
cur->used += 1;
return cur->tex;
}
}
}
return NULL;
}
/**
* @brief Adds a texture to the list under the name of path.
*/
static int gl_texAdd( glTexture *tex )
{
glTexList *new, *cur, *last;
/* Create the new node */
new = malloc( sizeof(glTexList) );
new->next = NULL;
new->used = 1;
new->tex = tex;
if (texture_list == NULL) /* special condition - creating new list */
texture_list = new;
else {
for (cur=texture_list; cur!=NULL; cur=cur->next)
last = cur;
last->next = new;
}
return 0;
}
/**
* @brief Loads an image as a texture.
*
* May not necessarily load the image but use one if it's already open.
*
* @param path Image to load.
* @param flags Flags to control image parameters.
* @return Texture loaded from image.
*/
glTexture* gl_newImage( const char* path, const unsigned int flags )
{
glTexture *t;
/* Check if it already exists. */
t = gl_texExists( path );
if (t != NULL)
return t;
/* Load the image */
return gl_loadNewImage( path, flags );
}
/**
* @brief Only loads the image, does not add to stack unlike gl_newImage.
*
* @param path Image to load.
* @param flags Flags to control image parameters.
* @return Texture loaded from image.
*/
static glTexture* gl_loadNewImage( const char* path, const unsigned int flags )
{
glTexture *texture;
SDL_Surface *surface;
SDL_RWops *rw;
npng_t *npng;
png_uint_32 w, h;
int sx, sy;
char *str;
int len;
/* load from packfile */
rw = ndata_rwops( path );
if (rw == NULL) {
WARN("Failed to load surface '%s' from ndata.", path);
return NULL;
}
npng = npng_open( rw );
if (npng == NULL) {
WARN("File '%s' is not a png.", path );
return NULL;
}
npng_dim( npng, &w, &h );
/* Process metadata. */
len = npng_metadata( npng, "sx", &str );
sx = (len > 0) ? atoi(str) : 1;
len = npng_metadata( npng, "sy", &str );
sy = (len > 0) ? atoi(str) : 1;
/* Load surface. */
surface = npng_readSurface( npng, gl_needPOT(), 1 );
npng_close( npng );
if (surface == NULL) {
WARN("'%s' could not be opened", path );
SDL_RWclose( rw );
return NULL;
}
if (flags & OPENGL_TEX_MAPTRANS)
texture = gl_loadImagePadTrans( path, surface, rw, flags, w, h, sx, sy, 1 );
else
texture = gl_loadImagePad( path, surface, flags, w, h, sx, sy, 1 );
SDL_RWclose( rw );
return texture;
}
/**
* @brief Loads the texture immediately, but also sets it as a sprite.
*
* @param path Image to load.
* @param sx Number of X sprites in image.
* @param sy Number of Y sprites in image.
* @param flags Flags to control image parameters.
* @return Texture loaded.
*/
glTexture* gl_newSprite( const char* path, const int sx, const int sy,
const unsigned int flags )
{
glTexture* texture;
texture = gl_newImage( path, flags );
if (texture == NULL)
return NULL;
/* will possibly overwrite an existing textur properties
* so we have to load same texture always the same sprites */
texture->sx = (double) sx;
texture->sy = (double) sy;
texture->sw = texture->w / texture->sx;
texture->sh = texture->h / texture->sy;
texture->srw = texture->sw / texture->rw;
texture->srh = texture->sh / texture->rh;
return texture;
}
/**
* @brief Frees a texture.
*
* @param texture Texture to free.
*/
void gl_freeTexture( glTexture* texture )
{
glTexList *cur, *last;
/* Shouldn't be NULL (won't segfault though) */
if (texture == NULL) {
WARN("Attempting to free NULL texture!");
return;
}
/* see if we can find it in stack */
last = NULL;
for (cur=texture_list; cur!=NULL; cur=cur->next) {
if (cur->tex == texture) { /* found it */
cur->used--;
if (cur->used <= 0) { /* not used anymore */
/* free the texture */
glDeleteTextures( 1, &texture->texture );
if (texture->trans != NULL)
free(texture->trans);
if (texture->name != NULL)
free(texture->name);
free(texture);
/* free the list node */
if (last == NULL) { /* case there's no texture before it */
if (cur->next != NULL)
texture_list = cur->next;
else /* case it's the last texture */
texture_list = NULL;
}
else
last->next = cur->next;
free(cur);
}
return; /* we already found it so we can exit */
}
last = cur;
}
/* Not found */
if (texture->name != NULL) /* Surfaces will have NULL names */
WARN("Attempting to free texture '%s' not found in stack!", texture->name);
/* Free anyways */
glDeleteTextures( 1, &texture->texture );
if (texture->trans != NULL)
free(texture->trans);
if (texture->name != NULL)
free(texture->name);
free(texture);
gl_checkErr();
}
/**
* @brief Duplicates a texture.
*
* @param texture Texture to duplicate.
* @return Duplicate of texture.
*/
glTexture* gl_dupTexture( glTexture *texture )
{
glTexList *cur;
/* No segfaults kthxbye. */
if (texture == NULL)
return NULL;
/* check to see if it already exists */
if (texture_list != NULL) {
for (cur=texture_list; cur!=NULL; cur=cur->next) {
if (texture == cur->tex) {
cur->used += 1;
return cur->tex;
}
}
}
/* Invalid texture. */
WARN("Unable to duplicate texture '%s'.", texture->name);
return NULL;
}
/**
* @brief Checks to see if a pixel is transparent in a texture.
*
* @param t Texture to check for transparency.
* @param x X position of the pixel.
* @param y Y position of the pixel.
* @return 1 if the pixel is transparent or 0 if it isn't.
*/
int gl_isTrans( const glTexture* t, const int x, const int y )
{
int i;
/* Get the position in the sheet. */
i = y*(int)(t->w) + x ;
/* Now we have to pull out the individual bit. */
return !(t->trans[ i/8 ] & (1 << (i%8)));
}
/**
* @brief Sets x and y to be the appropriate sprite for glTexture using dir.
*
* Very slow, try to cache if possible like the pilots do instead of using
* in O(n^2) or worse functions.
*
* @param[out] x X sprite to use.
* @param[out] y Y sprite to use.
* @param t Texture to get sprite from.
* @param dir Direction to get sprite from.
*/
void gl_getSpriteFromDir( int* x, int* y, const glTexture* t, const double dir )
{
int s, sx, sy;
double shard, rdir;
#ifdef DEBUGGING
if ((dir > 2.*M_PI) || (dir < 0.)) {
WARN("Angle not between 0 and 2.*M_PI [%f].", dir);
return;
}
#endif /* DEBUGGING */
/* what each image represents in angle */
shard = 2.*M_PI / (t->sy*t->sx);
/* real dir is slightly moved downwards */
rdir = dir + shard/2.;
/* now calculate the sprite we need */
s = (int)(rdir / shard);
sx = t->sx;
sy = t->sy;
/* makes sure the sprite is "in range" */
if (s > (sy*sx-1))
s = s % (sy*sx);
(*x) = s % sx;
(*y) = s / sx;
}
/**
* @brief Checks to see if OpenGL needs POT textures.
*
* @return 0 if OpenGL doesn't needs POT textures.
*/
int gl_needPOT (void)
{
if (gl_tex_ext_npot && conf.npot)
return 0;
else
return 1;
}
/**
* @brief Initializes the opengl texture subsystem.
*
* @return 0 on success.
*/
int gl_initTextures (void)
{
if (gl_hasVersion(2,0) || gl_hasExt("GL_ARB_texture_non_power_of_two"))
gl_tex_ext_npot = 1;
return 0;
}
/**
* @brief Cleans up the opengl texture subsystem.
*/
void gl_exitTextures (void)
{
glTexList *tex;
/* Make sure there's no texture leak */
if (texture_list != NULL) {
DEBUG("Texture leak detected!");
for (tex=texture_list; tex!=NULL; tex=tex->next)
DEBUG(" '%s' opened %d times", tex->tex->name, tex->used );
}
}