warzone2100/src/texture.c

260 lines
8.1 KiB
C
Raw Normal View History

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection Project
Warzone 2100 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.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file texture.c
* This is where we do texture atlas generation.
*/
#include "lib/framework/frame.h"
#include <string.h>
#include <physfs.h>
#include <SDL_opengl.h>
#ifdef __APPLE__
#include <opengl/glu.h>
#else
#include <GL/glu.h>
#endif
#include "lib/framework/file.h"
#include "lib/ivis_common/pietypes.h"
#include "lib/ivis_common/piestate.h"
#include "lib/ivis_common/tex.h"
#include "lib/ivis_common/piepalette.h"
#include "lib/ivis_opengl/screen.h"
#include "display3ddef.h"
#include "texture.h"
#include "radar.h"
#define MIPMAP_LEVELS 4
#define MIPMAP_MIN 16
#define MIPMAP_NORMAL 64
#define MIPMAP_MAX 128
/* Texture page and coordinates for each tile */
TILE_TEX_INFO tileTexInfo[MAX_TILES];
static int firstPage; // the last used page before we start adding terrain textures
int terrainPage; // texture ID of the terrain page
static int mipmap_max, mipmap_levels, mipmap_user_requested = MIPMAP_NORMAL;
void setTextureSize(int texSize)
{
if (texSize < 16 || texSize % 16 != 0)
{
debug(LOG_ERROR, "Attempted to set bad texture size %d! Ignored.", texSize);
return;
}
mipmap_user_requested = texSize;
debug(LOG_3D, "texture size set to %d", texSize);
}
int getTextureSize()
{
return mipmap_user_requested;
}
// Generate a new texture page both in the texture page table, and on the graphics card
static int newPage(const char *name, int level, int width, int height, int count)
{
int texPage = firstPage + ((count + 1) / TILES_IN_PAGE);
// debug(LOG_TEXTURE, "newPage: texPage=%d firstPage=%d %s %d (%d,%d) (count %d + 1) / %d _TEX_INDEX=%u",
// texPage, firstPage, name, level, width, height, count, TILES_IN_PAGE, _TEX_INDEX);
if (texPage == _TEX_INDEX)
{
// We need to create a new texture page; create it and increase texture table to store it
glGenTextures(1, (GLuint *) &_TEX_PAGE[texPage].id);
_TEX_INDEX++;
}
terrainPage = texPage;
ASSERT(_TEX_INDEX > texPage, "newPage: Index too low (%d > %d)", _TEX_INDEX, texPage);
ASSERT(_TEX_INDEX < iV_TEX_MAX, "Too many texture pages used");
strlcpy(_TEX_PAGE[texPage].name, name, sizeof(_TEX_PAGE[texPage].name));
pie_SetTexturePage(texPage);
// Specify first and last mipmap level to be used
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmap_levels - 1);
// debug(LOG_TEXTURE, "newPage: glTexImage2D(page=%d, level=%d) opengl id=%u", texPage, level, _TEX_PAGE[texPage].id);
glTexImage2D(GL_TEXTURE_2D, level, wz_texture_compression, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Use anisotropic filtering, if available, but only max 4.0 to reduce processor burden
if (check_extension("GL_EXT_texture_filter_anisotropic"))
{
GLfloat max;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, MIN(4.0f, max));
}
return texPage;
}
void texLoad(const char *fileName)
{
char fullPath[PATH_MAX], partialPath[PATH_MAX], *buffer;
unsigned int i, j, k, size;
int texPage;
GLint glval;
firstPage = _TEX_INDEX;
ASSERT(_TEX_INDEX < iV_TEX_MAX, "Too many texture pages used");
ASSERT(MIPMAP_MAX == TILE_WIDTH && MIPMAP_MAX == TILE_HEIGHT, "Bad tile sizes");
// reset defaults
mipmap_max = MIPMAP_MAX;
mipmap_levels = MIPMAP_LEVELS;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glval);
while (glval < mipmap_max * TILES_IN_PAGE_COLUMN)
{
mipmap_max /= 2;
mipmap_levels--;
debug(LOG_ERROR, "Max supported texture size %dx%d is too low - reducing texture detail to %dx%d.",
(int)glval, (int)glval, mipmap_max, mipmap_max);
ASSERT(mipmap_levels > 0, "Supported texture size %d is too low to load any mipmap levels!",
(int)glval);
if (mipmap_levels == 0)
{
exit(1);
}
}
while (mipmap_user_requested < mipmap_max)
{
mipmap_max /= 2;
mipmap_levels--;
debug(LOG_3D, "Downgrading texture quality to %d due to user setting %d", mipmap_max, mipmap_user_requested);
}
mipmap_user_requested = mipmap_max; // reduce to lowest possible
/* Get and set radar colours */
sprintf(fullPath, "%s.radar", fileName);
if (!loadFile(fullPath, &buffer, &size))
{
debug(LOG_ERROR, "texLoad: Could not find radar colours at %s", fullPath);
abort(); // cannot recover; we could possibly generate a random palette?
}
i = 0; // tile
k = 0; // number of values read
j = 0; // place in buffer
do {
unsigned int r, g, b;
int cnt = 0;
k = sscanf(buffer + j, "%2x%2x%2x%n", &r, &g, &b, &cnt);
j += cnt;
if (k >= 3)
{
radarColour(i, r, g, b);
}
i++; // next tile
} while (k >= 3 && j + 6 < size);
free(buffer);
/* Now load the actual tiles */
i = mipmap_max; // i is used to keep track of the tile dimensions
for (j = 0; j < mipmap_levels; j++)
{
int xOffset = 0, yOffset = 0; // offsets into the texture atlas
int xSize = 1;
int ySize = 1;
const int xLimit = TILES_IN_PAGE_COLUMN * i;
const int yLimit = TILES_IN_PAGE_ROW * i;
// pad width and height into ^2 values
while (xLimit > (xSize *= 2));
while (yLimit > (ySize *= 2));
// Generate the empty texture buffer in VRAM
texPage = newPage(fileName, j, xSize, ySize, 0);
sprintf(partialPath, "%s-%d", fileName, i);
// Load until we cannot find anymore of them
for (k = 0; k < MAX_TILES; k++)
{
iV_Image tile;
sprintf(fullPath, "%s/tile-%02d.png", partialPath, k);
if (PHYSFS_exists(fullPath)) // avoid dire warning
{
BOOL retval = iV_loadImage_PNG(fullPath, &tile);
ASSERT(retval, "texLoad: Could not load %s", fullPath);
}
else
{
// no more textures in this set
ASSERT(k > 0, "texLoad: Could not find %s", fullPath);
break;
}
// Insert into texture page
glTexSubImage2D(GL_TEXTURE_2D, j, xOffset, yOffset, tile.width, tile.height,
GL_RGBA, GL_UNSIGNED_BYTE, tile.bmp);
free(tile.bmp);
if (i == mipmap_max) // dealing with main texture page; so register coordinates
{
tileTexInfo[k].uOffset = (float)xOffset / (float)xSize;
tileTexInfo[k].vOffset = (float)yOffset / (float)ySize;
tileTexInfo[k].texPage = texPage;
debug(LOG_TEXTURE, " texLoad: Registering k=%d i=%d u=%f v=%f xoff=%d yoff=%d xsize=%d ysize=%d tex=%d (%s)",
k, i, tileTexInfo[k].uOffset, tileTexInfo[k].vOffset, xOffset, yOffset, xSize, ySize, texPage, fullPath);
}
xOffset += i; // i is width of tile
if (xOffset + i > xLimit)
{
yOffset += i; // i is also height of tile
xOffset = 0;
}
if (yOffset + i > yLimit)
{
/* Change to next texture page */
xOffset = 0;
yOffset = 0;
debug(LOG_TEXTURE, "texLoad: Extra page added at %d for %s, was page %d, opengl id %u",
k, partialPath, texPage, _TEX_PAGE[texPage].id);
texPage = newPage(fileName, j, xSize, ySize, k);
}
}
debug(LOG_TEXTURE, "texLoad: Found %d textures for %s mipmap level %d, added to page %d, opengl id %u",
k, partialPath, i, texPage, _TEX_PAGE[texPage].id);
i /= 2; // halve the dimensions for the next series; OpenGL mipmaps start with largest at level zero
}
}