Files
SRB2Kart-Saturn/src/hardware/hw_cache.cpp
T

1457 lines
37 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2019 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file
/// \brief load and convert graphics to the hardware format
#include "../doomdef.h"
#ifdef HWRENDER
#include <unordered_set>
#include "hw_main.h"
#include "hw_glob.h"
#include "hw_gl.h"
#include "hw_batching.h"
#include "../doomstat.h" //gamemode
#include "../i_video.h" //rendermode
#include "../r_data.h"
#include "../w_wad.h"
#include "../z_zone.h"
#include "../v_video.h"
#include "../r_draw.h"
#include "../r_main.h"
#include "../r_patch.h"
#include "../p_setup.h" // levelflats
#include "../p_spec.h" // anim_t
#include "../r_sky.h"
INT32 patchformat = GL_TEXFMT_AP_88; // use alpha for holes
INT32 textureformat = GL_TEXFMT_P_8; // use chromakey for hole
RGBA_t mapPalette[256] = {}; // the palette for the currently loaded level or menu etc.
static void HWR_FreeTextureColormaps(patch_t *patch);
static INT32 format2bpp(GLTextureFormat_t format)
{
switch (format)
{
case GL_TEXFMT_RGBA:
return 4;
case GL_TEXFMT_ALPHA_INTENSITY_88:
case GL_TEXFMT_AP_88:
return 2;
default:
return 1;
}
}
// This code was originally placed directly in HWR_DrawPatchInCache.
// It is now split from it for my sanity! (and the sanity of others)
// -- Monster Iestyn (13/02/19)
static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipmap_t *mipmap,
INT32 pblockheight, INT32 blockmodulo,
fixed_t yfracstep, fixed_t scale_y,
texpatch_t *originPatch, INT32 patchheight,
INT32 bpp, RGBA_t *palette)
{
fixed_t yfrac, position, count;
UINT8 *dest;
const UINT8 *source;
INT32 topdelta, prevdelta = -1;
INT32 originy = 0;
// for writing a pixel to dest
RGBA_t colortemp;
UINT8 alpha;
UINT8 texel;
UINT16 texelu16;
(void)patchheight; // This parameter is unused
if (originPatch) // originPatch can be NULL here, unlike in the software version
originy = originPatch->originy;
while (patchcol->topdelta != 0xff)
{
topdelta = patchcol->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
source = (const UINT8 *)patchcol + 3;
count = ((patchcol->length * scale_y) + (FRACUNIT/2)) >> FRACBITS;
position = originy + topdelta;
yfrac = 0;
if (position < 0)
{
yfrac = -position<<FRACBITS;
count += (((position * scale_y) + (FRACUNIT/2)) >> FRACBITS);
position = 0;
}
position = std::max(((position * scale_y) + (FRACUNIT/2)) >> FRACBITS, 0);
if (position + count >= pblockheight)
count = pblockheight - position;
dest = block + (position*blockmodulo);
while (count > 0)
{
count--;
texel = source[yfrac>>FRACBITS];
alpha = 0xFF;
// Make pixel transparent if chroma keyed
if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
alpha = 0x00;
//Hurdler: 25/04/2000: now support colormap in hardware mode
if (mipmap->colormap)
texel = mipmap->colormap->data[texel];
// hope compiler will get this switch out of the loops (dreams...)
// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
// Alam: SRB2 uses Mingw, HUGS
switch (bpp)
{
case 2 : // uhhhhhhhh..........
texelu16 = (UINT16)((alpha<<8) | texel);
memcpy(dest, &texelu16, sizeof(UINT16));
break;
case 3 : colortemp = palette[texel];
memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
break;
case 4 : colortemp = palette[texel];
colortemp.s.alpha = alpha;
memcpy(dest, &colortemp, sizeof(RGBA_t));
break;
// default is 1
default: *dest = texel;
break;
}
dest += blockmodulo;
yfrac += yfracstep;
}
patchcol = (const column_t *)((const UINT8 *)patchcol + patchcol->length + 4);
}
}
// Simplified patch caching function
// for use by sprites and other patches that are not part of a wall texture
// no alpha or flipping should be present since we do not want non-texture graphics to have them
// no offsets are used either
// -- Monster Iestyn (13/02/19)
static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
INT32 pblockwidth, INT32 pblockheight,
INT32 pwidth, INT32 pheight,
const patch_t *realpatch)
{
INT32 ncols;
fixed_t xfrac, xfracstep;
fixed_t yfracstep, scale_y;
const column_t *patchcol;
UINT8 *block = static_cast<UINT8 *>(mipmap->data);
INT32 bpp;
INT32 blockmodulo;
RGBA_t *palette;
if (pwidth <= 0 || pheight <= 0)
return;
palette = HWR_GetTexturePalette();
ncols = pwidth;
// source advance
xfrac = 0;
xfracstep = FRACUNIT;
yfracstep = FRACUNIT;
scale_y = FRACUNIT;
bpp = format2bpp(mipmap->format);
if (bpp < 1 || bpp > 4)
I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
// NOTE: should this actually be pblockwidth*bpp?
blockmodulo = pblockwidth*bpp;
// Draw each column to the block cache
for (; ncols--; block += bpp, xfrac += xfracstep)
{
patchcol = (const column_t *)((const UINT8 *)realpatch->columns + (realpatch->columnofs[xfrac>>FRACBITS]));
HWR_DrawColumnInCache(patchcol, block, mipmap,
pblockheight, blockmodulo,
yfracstep, scale_y,
NULL, pheight, // not that pheight is going to get used anyway...
bpp, palette);
}
}
// This function we use for caching patches that belong to textures
static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
INT32 pblockwidth, INT32 pblockheight,
texture_t *texture, texpatch_t *patch,
const softwarepatch_t *realpatch)
{
INT32 x, x1, x2;
INT32 col, ncols;
fixed_t xfrac, xfracstep;
fixed_t yfracstep, scale_y;
const column_t *patchcol;
UINT8 *block = static_cast<UINT8 *>(mipmap->data);
INT32 bpp;
INT32 blockmodulo;
INT32 width, height;
RGBA_t *palette;
if (texture->width <= 0 || texture->height <= 0)
return;
x1 = patch->originx;
width = SHORT(realpatch->width);
height = SHORT(realpatch->height);
x2 = x1 + width;
if (x1 > texture->width || x2 < 0)
return; // patch not located within texture's x bounds, ignore
if (patch->originy > texture->height || (patch->originy + height) < 0)
return; // patch not located within texture's y bounds, ignore
palette = HWR_GetTexturePalette();
// patch is actually inside the texture!
// now check if texture is partly off-screen and adjust accordingly
// left edge
if (x1 < 0)
x = 0;
else
x = x1;
// right edge
if (x2 > texture->width)
x2 = texture->width;
col = x * pblockwidth / texture->width;
ncols = ((x2 - x) * pblockwidth) / texture->width;
// source advance
xfrac = 0;
if (x1 < 0)
xfrac = -x1<<FRACBITS;
xfracstep = (texture->width << FRACBITS) / pblockwidth;
yfracstep = (texture->height<< FRACBITS) / pblockheight;
scale_y = (pblockheight << FRACBITS) / texture->height;
bpp = format2bpp(mipmap->format);
if (bpp < 1 || bpp > 4)
I_Error("HWR_DrawTexturePatchInCache: no drawer defined for this bpp (%d)\n",bpp);
// NOTE: should this actually be pblockwidth*bpp?
blockmodulo = pblockwidth*bpp;
// Draw each column to the block cache
for (block += col*bpp; ncols--; block += bpp, xfrac += xfracstep)
{
patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
HWR_DrawColumnInCache(patchcol, block, mipmap,
pblockheight, blockmodulo,
yfracstep, scale_y,
patch, height,
bpp, palette);
}
}
static UINT8 *MakeBlock(GLMipmap_t *glMipmap)
{
UINT8 *block;
INT32 bpp, i;
UINT16 bu16 = ((0x00 <<8) | HWR_PATCHES_CHROMAKEY_COLORINDEX);
INT32 blocksize = (glMipmap->width * glMipmap->height);
bpp = format2bpp(glMipmap->format);
block = static_cast<UINT8 *>(Z_Malloc(blocksize*bpp, PU_HWRCACHE, &(glMipmap->data)));
switch (bpp)
{
case 1: memset(block, HWR_PATCHES_CHROMAKEY_COLORINDEX, blocksize); break;
case 2:
// fill background with chromakey, alpha = 0
for (i = 0; i < blocksize; i++)
memcpy(block+i*sizeof(UINT16), &bu16, sizeof(UINT16));
break;
case 4: memset(block, 0x00, blocksize*sizeof(UINT32)); break;
}
return block;
}
//
// Create a composite texture from patches, adapt the texture size to a power of 2
// height and width for the hardware texture cache.
//
static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *gltex, boolean noencore)
{
UINT8 *block;
texture_t *texture;
texpatch_t *patch;
softwarepatch_t *realpatch;
UINT8 *pdata;
INT32 blockwidth, blockheight, blocksize;
UINT8 *colormap = colormaps;
INT32 i, idx;
boolean skyspecial = false; // poor hack for Legacy large skies..
texture = textures[texnum];
gltex->mipmap.flags = TF_CHROMAKEYED | TF_WRAPXY;
gltex->mipmap.format = static_cast<GLTextureFormat_t>(textureformat);
// hack the Legacy skies..
if (UNLIKELY(memcmp(texture->name, "SKY", 3) == 0 &&
(texture->name[4] == 0 || texture->name[5] == 0)))
{
skyspecial = true;
gltex->mipmap.flags &= ~TF_CHROMAKEYED; // don't use the chromakey for sky
gltex->mipmap.format = GL_TEXFMT_RGBA; // that skyspecial code below assumes this format ...
}
gltex->mipmap.width = (UINT16)(texture->width);
gltex->mipmap.height = (UINT16)(texture->height);
#ifdef GLENCORE
if (encoremap && !noencore)
colormap += COLORMAP_REMAPOFFSET;
#endif
gltex->mipmap.colormap = static_cast<GLColormap_t *>(Z_Calloc(sizeof(*gltex->mipmap.colormap), PU_HWRPATCHCOLMIPMAP, NULL));
gltex->mipmap.colormap->source = colormap;
memcpy(gltex->mipmap.colormap->data, colormap, 256 * sizeof(UINT8));
blockwidth = texture->width;
blockheight = texture->height;
blocksize = (blockwidth * blockheight);
block = MakeBlock(&gltex->mipmap);
if (UNLIKELY(skyspecial)) // Hurdler: not efficient, but better than holes in the sky (and it's done only at level loading)
{
INT32 j;
RGBA_t col;
const RGBA_t *palette = HWR_GetTexturePalette();
col = palette[HWR_CHROMAKEY_EQUIVALENTCOLORINDEX];
for (idx = 0, j = 0; j < blockheight; j++)
{
for (i = 0; i < blockwidth; i++, idx++)
{
block[4*idx+0] = col.s.red;
block[4*idx+1] = col.s.green;
block[4*idx+2] = col.s.blue;
block[4*idx+3] = 0xff;
}
}
}
// Composite the columns together.
for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
{
pdata = static_cast<UINT8 *>(W_CacheLumpNumPwad(patch->wad, patch->lump, PU_LEVEL));
realpatch = (softwarepatch_t *)pdata;
HWR_DrawTexturePatchInCache(&gltex->mipmap, blockwidth, blockheight, texture, patch, realpatch);
}
//Hurdler: not efficient at all but I don't remember exactly how HWR_DrawPatchInCache works :(
if (format2bpp(gltex->mipmap.format) == 4)
{
for (i = 3; i < blocksize*4; i += 4) // blocksize*4 because blocksize doesn't include the bpp
{
if (block[i] != 0)
continue;
gltex->mipmap.flags |= TF_TRANSPARENT;
break;
}
}
gltex->scaleX = 1.0f/(texture->width*FRACUNIT);
gltex->scaleY = 1.0f/(texture->height*FRACUNIT);
}
// patch may be NULL if glMipmap has been initialised already and makebitmap is false
void HWR_MakePatch(const patch_t *patch, GLPatch_t *glPatch, GLMipmap_t *glMipmap, boolean makebitmap)
{
if (glMipmap->width == 0)
{
glMipmap->width = glMipmap->height = 1;
while (glMipmap->width < patch->width) glMipmap->width <<= 1;
while (glMipmap->height < patch->height) glMipmap->height <<= 1;
// no wrap around, no chroma key
glMipmap->flags = 0;
// setup the texture info
glMipmap->format = static_cast<GLTextureFormat_t>(patchformat);
glPatch->max_s = (float)patch->width / (float)glMipmap->width;
glPatch->max_t = (float)patch->height / (float)glMipmap->height;
}
Z_Free(glMipmap->data);
glMipmap->data = NULL;
if (makebitmap)
{
MakeBlock(glMipmap);
HWR_DrawPatchInCache(glMipmap,
glMipmap->width, glMipmap->height,
patch->width, patch->height,
patch);
}
}
// =================================================
// CACHING HANDLING
// =================================================
typedef struct
{
GLMapTexture_t normal;
#ifdef GLENCORE
GLMapTexture_t encore;
#endif
} GLMapTextureSet_t; // idk i suck at naming things
static size_t gl_numtextures = 0; // Texture count
static GLMapTextureSet_t *gl_textures; // For all textures
static void HWR_FreeTextureData(patch_t *patch)
{
GLPatch_t *glPatch;
if (!patch || !patch->hardware)
return;
glPatch = static_cast<GLPatch_t *>(patch->hardware);
if (vid.glstate == VID_GL_LIBRARY_LOADED)
GL_DeleteTexture(glPatch->mipmap);
Z_Free(glPatch->mipmap->data);
glPatch->mipmap->data = NULL;
}
void HWR_FreeTexture(patch_t *patch)
{
if (!patch)
return;
if (patch->hardware)
{
GLPatch_t *glPatch = static_cast<GLPatch_t *>(patch->hardware);
HWR_FreeTextureColormaps(patch);
if (glPatch->mipmap)
{
HWR_FreeTextureData(patch);
Z_Free(glPatch->mipmap);
glPatch->mipmap = NULL;
}
Z_Free(patch->hardware);
}
patch->hardware = NULL;
}
// Called by HWR_FreePatchCache.
static void HWR_FreeTextureColormaps(patch_t *patch)
{
GLPatch_t *pat;
// The patch must be valid, obviously
if (!patch)
return;
pat = static_cast<GLPatch_t *>(patch->hardware);
if (!pat)
return;
// The mipmap must be valid, obviously
while (pat->mipmap)
{
// Confusing at first, but pat->mipmap->nextcolormap
// at the beginning of the loop is the first colormap
// from the linked list of colormaps.
GLMipmap_t *next = NULL;
// No mipmap in this patch, break out of the loop.
if (!pat->mipmap)
break;
// No colormap mipmaps either.
if (!pat->mipmap->nextcolormap)
break;
// Set the first colormap to the one that comes after it.
next = pat->mipmap->nextcolormap;
pat->mipmap->nextcolormap = next->nextcolormap;
// Free image data from memory.
Z_Free(next->data);
Z_Free(next->colormap);
next->data = NULL;
next->colormap = NULL;
GL_DeleteTexture(next);
// Free the old colormap mipmap from memory.
free(next);
}
}
static boolean FreeTextureCallback(void *mem)
{
patch_t *patch = (patch_t *)mem;
HWR_FreeTexture(patch);
return false;
}
static boolean FreeColormapsCallback(void *mem)
{
patch_t *patch = (patch_t *)mem;
HWR_FreeTextureColormaps(patch);
return false;
}
static void HWR_FreePatchCache(boolean freeall)
{
boolean (*callback)(void *mem) = FreeTextureCallback;
if (!freeall)
callback = FreeColormapsCallback;
//Z_IterateTags(PU_PATCH, PU_PATCH_ROTATED, callback);
Z_IterateTags(PU_PATCH, PU_PATCH, callback);
Z_IterateTags(PU_SPRITE, PU_HUDGFX, callback);
}
// free all textures after each level
void HWR_ClearAllTextures(void)
{
// free references to the textures
GL_ClearMipMapCache();
HWR_FreePatchCache(true);
}
/*static void HWR_FreeColormapCache(void)
{
HWR_FreePatchCache(false);
}*/
void HWR_InitMapTextures(void)
{
gl_textures = NULL;
}
static void FreeMapTexture(GLMapTexture_t *tex)
{
if (!tex)
return;
GL_DeleteTexture(&tex->mipmap);
Z_Free(tex->mipmap.data);
tex->mipmap.data = NULL;
}
void HWR_FreeMapTextures(void)
{
size_t i;
if (!gl_textures)
{
gl_numtextures = 0;
return;
}
for (i = 0; i < gl_numtextures; i++)
{
FreeMapTexture(&gl_textures[i].normal);
#ifdef GLENCORE
FreeMapTexture(&gl_textures[i].encore);
#endif
}
// now the heap don't have any 'user' pointing to our
// texturecache info, we can free it
free(gl_textures);
gl_textures = NULL;
gl_numtextures = 0;
}
static void HWR_PrecacheLevelFlats(void)
{
// lookup for flats that may´ve been already loaded
// too lazy to make a hashtable or smth so unordered set will do :carbuncle:
std::unordered_set<UINT32> flatpresent;
size_t i, j;
INT32 k;
auto load_flat = [&](lumpnum_t lump, boolean noencoremap)
{
// flat already loaded?
if (flatpresent.count(lump) == 0)
{
HWR_GetFlat(lump, noencoremap);
flatpresent.insert(lump);
}
};
// special case for encore
#ifdef GLENCORE
if (encoremap)
{
// go through all sectors to determine if it should be remapped for encore
for (i = 0; i < numsectors; i++)
{
sector_t *sec = &sectors[i];
// gotta check sector floor and ceiling
for (j = 0; j < 2; j++)
{
const boolean ceiling = (j == 0);
const INT32 pic = ceiling ? sec->ceilingpic : sec->floorpic;
const levelflat_t *levelflat = &levelflats[pic];
load_flat(levelflat->lumpnum, R_NoEncore(sec, ceiling));
if (levelflat->speed) // is it an animated flat ?
{
// start from 0, occasionally basenumlump != flat->lumpnum
for (k = 0; k < levelflat->numpics; k++)
{
load_flat(levelflat->baselumpnum + k, R_NoEncore(sec, ceiling));
}
}
}
}
}
else
#endif
{
// on non encore we have it simple
// just load every flat in the level
for (i = 0; i < numlevelflats; i++)
{
const levelflat_t *levelflat = &levelflats[i];
load_flat(levelflat->lumpnum, false);
if (levelflat->speed) // is it an animated flat ?
{
// start from 0, occasionally basenumlump != flat->lumpnum
for (k = 0; k < levelflat->numpics; k++)
{
load_flat(levelflat->baselumpnum + k, false);
}
}
}
}
}
static void HWR_PrecacheLevelTextures(void)
{
char *texturepresent;
anim_t *anim;
size_t i, j, f;
INT32 h;
texturepresent = static_cast<char *>(calloc(gl_numtextures, sizeof(*texturepresent)));
if (texturepresent == NULL)
I_Error("%s: Out of memory looking up textures", "HWR_PrecacheLevel");
// Sky texture is always present.
// Note that F_SKY1 is the name used to indicate a sky floor/ceiling as a flat,
// while the sky texture is stored like a wall texture, with a skynum dependent name.
texturepresent[skytexture] = 1;
HWR_GetTexture(skytexture, false);
for (i = 0; i < numlines; i++)
{
const line_t *line = &lines[i];
#ifdef GLENCORE
const int noencoremap = ((encoremap && (line->flags & ML_TFERLINE)) ? 2 : 1);
#endif
// two sides
for (j = 0; j < 2; j++)
{
// check if single-sided linedef
if (line->sidenum[j] == 0xffff)
continue;
const side_t *side = &sides[line->sidenum[j]];
const INT32 sidetex[] = {side->toptexture, side->midtexture, side->bottomtexture};
// gotta check from top to bottom uwu
for (f = 0; f < 3; f++)
{
const INT32 texnum = sidetex[f];
if (texnum < 0 || texnum >= (signed)gl_numtextures)
continue;
if (texturepresent[texnum])
continue;
#ifdef GLENCORE
texturepresent[texnum] = 1|noencoremap;
#else
texturepresent[texnum] = 1;
#endif
HWR_GetTexture(texnum, false);
#ifdef GLENCORE
if (noencoremap & 2)
HWR_GetTexture(texnum, true);
#endif
}
}
}
for (anim = anims; anim < lastanim; anim++)
{
if (!anim->istexture)
continue;
const char texpresent = texturepresent[anim->basepic];
if (!texpresent)
continue;
for (h = 1; h < anim->numpics; h++)
{
const INT32 animtexnum = anim->basepic+h;
if (texpresent & 1)
{
HWR_GetTexture(animtexnum, false);
}
#ifdef GLENCORE
if (texpresent & 2)
{
HWR_GetTexture(animtexnum, true);
}
#endif
}
}
free(texturepresent);
}
/*static void HWR_PrecacheLevelSprites(void)
{
patch_t *spritepatch;
char *spritepresent;
size_t i, j, k;
thinker_t *th;
spritepresent = calloc(numsprites, sizeof (*spritepresent));
if (spritepresent == NULL)
I_Error("%s: Out of memory looking up sprites", "HWR_PrecacheLevel");
for (th = thinkercap.next; th != &thinkercap; th = th->next)
{
if (th->function != (actionf_p1)P_MobjThinker)
continue;
const mobj_t *mo = (mobj_t *)th;
// ogl is weird
// for some reason it does not want to preload sprites with colormaps
// so just save us the work
if (mo->color || mo->colorized)
continue;
spritepresent[mo->sprite] = 1;
}
for (i = 0; i < numsprites; i++)
{
if (!spritepresent[i])
continue;
for (j = 0; j < sprites[i].numframes; j++)
{
const spriteframe_t *sf = &sprites[i].spriteframes[j];
#define cacheang(a) {\
const lumpnum_t lump = sf->lumppat[a];\
spritepatch = (patch_t *)W_CachePatchNum(lump, PU_SPRITE);\
if (spritepatch != NULL)\
HWR_GetPatch(spritepatch);\
}
// see R_InitSprites for more about lumppat,lumpid
switch (sf->rotate)
{
case SRF_SINGLE:
cacheang(0);
break;
case SRF_2D:
cacheang(2);
cacheang(6);
break;
default:
k = 8;
while (k--)
cacheang(k);
break;
}
#undef cacheang
}
}
free(spritepresent);
}*/
void HWR_PrecacheLevel(void)
{
if (gamestate != GS_LEVEL || !cv_precachetextures.value)
return;
// Precache flats.
HWR_PrecacheLevelFlats();
// prevent timeouts
NetKeepAlive();
// Precache textures.
HWR_PrecacheLevelTextures();
// prevent timeouts
NetKeepAlive();
// Precache sprites.
//HWR_PrecacheLevelSprites();
// prevent timeouts
//NetKeepAlive();
}
void HWR_LoadMapTextures(size_t pnumtextures)
{
// we must free it since numtextures may have changed
HWR_FreeMapTextures();
gl_numtextures = pnumtextures;
gl_textures = static_cast<GLMapTextureSet_t *>(calloc(gl_numtextures, sizeof(*gl_textures))); // *2 - 1 for encore-remapped texture and another for noencore texture (unused when not in encore), see: GLMapTextureSet_t
if (gl_textures == NULL)
I_Error("HWR_LoadMapTextures: ran out of memory for OpenGL textures. Sad!");
}
// --------------------------------------------------------------------------
// Make sure texture is downloaded and set it as the source
// --------------------------------------------------------------------------
GLMapTexture_t *HWR_GetTexture(INT32 tex, boolean noencore)
{
GLMapTexture_t *gltex;
GLMipmap_t *glMipmap;
if (tex < 0 || tex >= (signed)gl_numtextures)
{
#ifdef PARANOIA
I_Error("HWR_GetTexture: Invalid texture ID %d", tex);
#else
tex = 0;
#endif
}
// Every texture in memory, stored in the
// hardware renderer's bit depth format. Wow!
#ifdef GLENCORE
gltex = (encoremap && !noencore) ? &gl_textures[tex].encore : &gl_textures[tex].normal;
#else
gltex = &gl_textures[tex];
#endif
glMipmap = &gltex->mipmap;
// Generate texture if missing from the cache
if (!glMipmap->downloaded)
{
if (!gltex->mipmap.data)
HWR_GenerateTexture(tex, gltex, noencore);
// If hardware does not have the texture, then call GL_SetTexture to upload it
GL_SetTexture(glMipmap);
}
HWR_SetCurrentTexture(glMipmap);
// The system-memory data can be purged now.
Z_ChangeTag(glMipmap->data, PU_HWRCACHE_UNLOCKED);
return gltex;
}
static void HWR_CacheFlat(GLMipmap_t *glMipmap, lumpnum_t flatlumpnum)
{
#ifdef GLENCORE
UINT8 *flat;
size_t steppy;
#endif
size_t size, pflatsize;
boolean haschromakey = false;
// setup the texture info
glMipmap->format = GL_TEXFMT_P_8;
glMipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
size = W_LumpLength(flatlumpnum);
switch (size)
{
case 4194304: // 2048x2048 lump
pflatsize = 2048;
break;
case 1048576: // 1024x1024 lump
pflatsize = 1024;
break;
case 262144:// 512x512 lump
pflatsize = 512;
break;
case 65536: // 256x256 lump
pflatsize = 256;
break;
case 16384: // 128x128 lump
pflatsize = 128;
break;
case 1024: // 32x32 lump
pflatsize = 32;
break;
default: // 64x64 lump
pflatsize = 64;
break;
}
glMipmap->width = glMipmap->height = (UINT16)pflatsize;
// the flat raw data needn't be converted with palettized textures
W_ReadLump(flatlumpnum, Z_Malloc(size, PU_HWRCACHE, &glMipmap->data));
#ifdef GLENCORE
flat = static_cast<UINT8 *>(glMipmap->data);
for (steppy = 0; steppy < size; steppy++)
{
if (flat[steppy] == HWR_PATCHES_CHROMAKEY_COLORINDEX)
{
haschromakey = true;
continue;
}
flat[steppy] = glMipmap->colormap->source[flat[steppy]];
}
#endif
if (haschromakey)
{
const char *flatname = W_CheckNameForNum(flatlumpnum);
// hack for gba rainbow roads cyan floors
if (UNLIKELY(memcmp(flatname, "GBA_RRF5", 8) == 0 && flatname[8] == 0))
{
glMipmap->flags &= ~TF_CHROMAKEYED;
}
}
}
// Download a Doom 'flat' to the hardware cache and make it ready for use
void HWR_GetFlat(lumpnum_t flatlumpnum, boolean noencoremap)
{
GLMipmap_t *glMipmap;
patch_t *patch;
UINT8 *colormap = colormaps;
if (flatlumpnum == LUMPERROR)
return;
patch = HWR_GetCachedGLPatch(flatlumpnum);
glMipmap = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
if (!glMipmap->colormap)
{
#ifdef GLENCORE
if (!noencoremap && encoremap)
colormap += COLORMAP_REMAPOFFSET;
#endif
glMipmap->colormap = static_cast<GLColormap_t *>(Z_Calloc(sizeof(*glMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL));
glMipmap->colormap->source = colormap;
memcpy(glMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
}
if (!glMipmap->downloaded)
{
if (!glMipmap->data)
HWR_CacheFlat(glMipmap, flatlumpnum);
// If hardware does not have the texture, then call GL_SetTexture to upload it
GL_SetTexture(glMipmap);
}
HWR_SetCurrentTexture(glMipmap);
// The system-memory data can be purged now.
Z_ChangeTag(glMipmap->data, PU_HWRCACHE_UNLOCKED);
}
// --------------------+
// HWR_LoadPatchMipmap : Generates a patch into a mipmap, usually the mipmap inside the patch itself
// --------------------+
static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *glMipmap)
{
GLPatch_t *glPatch = static_cast<GLPatch_t *>(patch->hardware);
if (!glMipmap->downloaded)
{
if (!glMipmap->data)
HWR_MakePatch(patch, glPatch, glMipmap, true);
// If hardware does not have the texture, then call GL_SetTexture to upload it
GL_SetTexture(glMipmap);
}
HWR_SetCurrentTexture(glMipmap);
// The system-memory data can be purged now.
Z_ChangeTag(glMipmap->data, PU_HWRCACHE_UNLOCKED);
}
// ----------------------+
// HWR_UpdatePatchMipmap : Updates a mipmap.
// ----------------------+
static void HWR_UpdatePatchMipmap(patch_t *patch, GLMipmap_t *glMipmap)
{
GLPatch_t *glPatch = static_cast<GLPatch_t *>(patch->hardware);
HWR_MakePatch(patch, glPatch, glMipmap, true);
// If hardware does not have the texture, then call GL_SetTexture to upload it
// If it does have the texture, then call GL_UpdateTexture to update it
if (!glMipmap->downloaded)
GL_SetTexture(glMipmap);
else
GL_UpdateTexture(glMipmap);
HWR_SetCurrentTexture(glMipmap);
// The system-memory data can be purged now.
Z_ChangeTag(glMipmap->data, PU_HWRCACHE_UNLOCKED);
}
// -----------------+
// HWR_GetPatch : Downloads a patch to the hardware cache and make it ready for use
// -----------------+
void HWR_GetPatch(patch_t *patch)
{
if (!patch->hardware)
Patch_CreateGL(patch);
HWR_LoadPatchMipmap(patch, ((GLPatch_t *)patch->hardware)->mipmap);
}
// -------------------+
// HWR_GetMappedPatch : Same as HWR_GetPatch for sprite color
// -------------------+
void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap)
{
GLPatch_t *glPatch;
GLMipmap_t *glMipmap, *newMipmap;
if (!patch->hardware)
Patch_CreateGL(patch);
glPatch = static_cast<GLPatch_t *>(patch->hardware);
// Blatant hack for encore colormapping aside...
if (colormap == colormaps || colormap == NULL || colormap == (const UINT8*)(COLORMAP_REMAPOFFSET))
{
// Load the default (green) color in hardware cache
HWR_GetPatch(patch);
return;
}
// search for the mipmap
// skip the first (no colormap translated)
for (glMipmap = glPatch->mipmap; glMipmap->nextcolormap;)
{
glMipmap = glMipmap->nextcolormap;
if (glMipmap->colormap && glMipmap->colormap->source == colormap)
{
if (memcmp(glMipmap->colormap->data, colormap, 256 * sizeof(UINT8)))
{
memcpy(glMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
HWR_UpdatePatchMipmap(patch, glMipmap);
}
else
HWR_LoadPatchMipmap(patch, glMipmap);
return;
}
}
// not found, create it!
// If we are here, the sprite with the current colormap is not already in hardware memory
//BP: WARNING: don't free it manually without clearing the cache of harware renderer
// (it have a liste of mipmap)
// this malloc is cleared in HWR_FreeColormapCache
// (...) unfortunately z_malloc fragment alot the memory :(so malloc is better
newMipmap = static_cast<GLMipmap_t *>(calloc(1, sizeof (*newMipmap)));
if (newMipmap == NULL)
I_Error("%s: Out of memory", "HWR_GetMappedPatch");
glMipmap->nextcolormap = newMipmap;
newMipmap->colormap = static_cast<GLColormap_t *>(Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL));
newMipmap->colormap->source = colormap;
memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
HWR_LoadPatchMipmap(patch, newMipmap);
}
void HWR_UnlockCachedPatch(GLPatch_t *glPatch)
{
if (!glPatch)
return;
Z_ChangeTag(glPatch->mipmap->data, PU_HWRCACHE_UNLOCKED);
}
patch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
{
lumpcache_t *lumpcache = wadfiles[wadnum]->patchcache;
if (!lumpcache[lumpnum])
{
void *ptr = Z_Calloc(sizeof(patch_t), PU_PATCH, &lumpcache[lumpnum]);
Patch_Create(NULL, 0, ptr);
Patch_AllocateHardwarePatch(static_cast<patch_t *>(ptr));
}
return (patch_t *)(lumpcache[lumpnum]);
}
patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
{
return HWR_GetCachedGLPatchPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
}
// Need to do this because they aren't powers of 2
static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32 pblockheight,
lumpnum_t fademasklumpnum, UINT16 fmwidth, UINT16 fmheight)
{
INT32 i,j;
fixed_t posx, posy, stepx, stepy;
UINT8 *block = static_cast<UINT8 *>(mipmap->data); // places the data directly into here
UINT8 *flat;
UINT8 *dest, *src, texel;
RGBA_t col;
const RGBA_t *palette = HWR_GetTexturePalette();
// Place the flats data into flat
W_ReadLump(fademasklumpnum, Z_Malloc(W_LumpLength(fademasklumpnum), PU_HWRCACHE, &flat));
stepy = ((INT32)fmheight<<FRACBITS)/pblockheight;
stepx = ((INT32)fmwidth<<FRACBITS)/pblockwidth;
posy = 0;
for (j = 0; j < pblockheight; j++)
{
posx = 0;
dest = &block[j*(mipmap->width)]; // 1bpp
src = &flat[(posy>>FRACBITS)*fmwidth];
for (i = 0; i < pblockwidth;i++)
{
// fademask bpp is always 1, and is used just for alpha
texel = src[(posx)>>FRACBITS];
col = palette[texel];
*dest = col.s.red; // take the red level of the colour and use it for alpha, as fademasks do
dest++;
posx += stepx;
}
posy += stepy;
}
Z_Free(flat);
}
static void HWR_CacheFadeMask(GLMipmap_t *glMipmap, lumpnum_t fademasklumpnum)
{
size_t size;
UINT16 fmheight = 0, fmwidth = 0;
// setup the texture info
glMipmap->format = GL_TEXFMT_ALPHA_8; // put the correct alpha levels straight in so I don't need to convert it later
glMipmap->flags = 0;
size = W_LumpLength(fademasklumpnum);
switch (size)
{
// None of these are powers of 2, so I'll need to do what is done for textures and make them powers of 2 before they can be used
case 256000: // 640x400
fmwidth = 640;
fmheight = 400;
break;
case 64000: // 320x200
fmwidth = 320;
fmheight = 200;
break;
case 16000: // 160x100
fmwidth = 160;
fmheight = 100;
break;
case 4000: // 80x50 (minimum)
fmwidth = 80;
fmheight = 50;
break;
default: // Bad lump
CONS_Alert(CONS_WARNING, "Fade mask lump of incorrect size, ignored\n"); // I should avoid this by checking the lumpnum in HWR_RunWipe
glMipmap->width = 0;
glMipmap->height = 0;
return;
}
// Thankfully, this will still work for this scenario
glMipmap->width = fmwidth;
glMipmap->height = fmheight;
MakeBlock(glMipmap);
HWR_DrawFadeMaskInCache(glMipmap, fmwidth, fmheight, fademasklumpnum, fmwidth, fmheight);
// I DO need to convert this because it isn't power of 2 and we need the alpha
}
void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
{
GLMipmap_t *glMipmap;
patch_t *patch;
patch = HWR_GetCachedGLPatch(fademasklumpnum);
glMipmap = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
if (!glMipmap->downloaded && !glMipmap->data)
HWR_CacheFadeMask(glMipmap, fademasklumpnum);
GL_SetTexture(glMipmap);
// The system-memory data can be purged now.
Z_ChangeTag(glMipmap->data, PU_HWRCACHE_UNLOCKED);
}
// =================================================
// PALETTE HANDLING
// =================================================
void HWR_SetPalette(RGBA_t *palette)
{
if (HWR_ShouldUsePaletteRendering())
{
// set the palette for palette postprocessing
if (cv_glpalettedepth.value == 16)
{
// crush to 16-bit rgb565, like software currently does in the standard configuration
// Note: Software's screenshots have the 24-bit palette, but the screen gets
// the 16-bit version! For making comparison screenshots either use an external screenshot
// tool or set the palette depth to 24 bits.
RGBA_t crushed_palette[256];
for (int i = 0; i < 256; i++)
{
float fred = (float)(palette[i].s.red >> 3);
float fgreen = (float)(palette[i].s.green >> 2);
float fblue = (float)(palette[i].s.blue >> 3);
crushed_palette[i].s.red = (UINT8)(fred / 31.0f * 255.0f);
crushed_palette[i].s.green = (UINT8)(fgreen / 63.0f * 255.0f);
crushed_palette[i].s.blue = (UINT8)(fblue / 31.0f * 255.0f);
crushed_palette[i].s.alpha = 255;
}
GL_SetScreenPalette(crushed_palette);
}
else
{
GL_SetScreenPalette(palette);
}
// this part is responsible for keeping track of the palette OUTSIDE of a level.
if (gamestate != GS_LEVEL || gamestate == GS_TITLESCREEN)
HWR_SetMapPalette();
}
else
{
// set the palette for the textures
GL_SetPalette(palette);
// reset mapPalette so next call to HWR_SetMapPalette will update everything correctly
memset(mapPalette, 0, sizeof(mapPalette));
// hardware driver will flush there own cache if cache is non paletized
// now flush data texture cache so 32 bit texture are recomputed
if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
{
Z_FreeTag(PU_HWRCACHE);
Z_FreeTag(PU_HWRCACHE_UNLOCKED);
}
}
}
static void HWR_SetPaletteLookup(RGBA_t *palette)
{
int r, g, b;
UINT8 *lut = static_cast<UINT8 *>(Z_Malloc(
HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*sizeof(UINT8),
PU_STATIC, NULL));
#define STEP_SIZE (256/HWR_PALETTE_LUT_SIZE)
for (b = 0; b < HWR_PALETTE_LUT_SIZE; b++)
{
for (g = 0; g < HWR_PALETTE_LUT_SIZE; g++)
{
for (r = 0; r < HWR_PALETTE_LUT_SIZE; r++)
{
lut[b*HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE+g*HWR_PALETTE_LUT_SIZE+r] =
NearestPaletteColor(r*STEP_SIZE, g*STEP_SIZE, b*STEP_SIZE, palette);
}
}
}
#undef STEP_SIZE
GL_SetPaletteLookup(lut);
Z_Free(lut);
}
// Updates mapPalette to reflect the loaded level or other game state.
// Textures are flushed if needed.
// Call this function only in palette rendering mode.
void HWR_SetMapPalette(void)
{
RGBA_t RGBA_converted[256];
RGBA_t *palette;
int i;
if (gamestate != GS_LEVEL || gamestate == GS_TITLESCREEN)
{
// outside of a level, pLocalPalette should have PLAYPAL ready for us
palette = pLocalPalette;
}
else
{
// in a level pLocalPalette might have a flash palette, but we
// want the map's original palette.
lumpnum_t lumpnum = W_GetNumForName(GetPalette());
size_t palsize = W_LumpLength(lumpnum);
UINT8 *RGB_data;
if (palsize < 768) // 256 * 3
I_Error("HWR_SetMapPalette: A programmer assumed palette lumps are at least 768 bytes long, but apparently this was a wrong assumption!\n");
RGB_data = static_cast<UINT8 *>(W_CacheLumpNum(lumpnum, PU_CACHE));
// we got the RGB palette now, but we need it in RGBA format.
for (i = 0; i < 256; i++)
{
RGBA_converted[i].s.red = *(RGB_data++);
RGBA_converted[i].s.green = *(RGB_data++);
RGBA_converted[i].s.blue = *(RGB_data++);
RGBA_converted[i].s.alpha = 255;
}
palette = RGBA_converted;
}
// check if the palette has changed from the previous one
if (memcmp(mapPalette, palette, sizeof(mapPalette)))
{
memcpy(mapPalette, palette, sizeof(mapPalette));
// in palette rendering mode, this means that all rgba textures now have wrong colors
// and the lookup table is outdated
HWR_SetPaletteLookup(mapPalette);
GL_SetPalette(mapPalette);
if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
{
Z_FreeTag(PU_HWRCACHE);
Z_FreeTag(PU_HWRCACHE_UNLOCKED);
}
}
}
// Creates a hardware lighttable from the supplied lighttable.
// Returns the id of the hw lighttable, usable in FSurfaceInfo.
static UINT32 HWR_CreateLightTable(UINT8 *lighttable)
{
UINT32 i, id;
RGBA_t *palette = HWR_GetTexturePalette();
RGBA_t *hw_lighttable = static_cast<RGBA_t *>(Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_STATIC, NULL));
// To make the palette index -> RGBA mapping easier for the shader,
// the hardware lighttable is composed of RGBA colors instead of palette indices.
for (i = 0; i < 256 * 32; i++)
hw_lighttable[i] = palette[lighttable[i]];
id = GL_CreateLightTable(hw_lighttable);
Z_Free(hw_lighttable);
return id;
}
// get hwr lighttable id for colormap, create it if it doesn't already exist
UINT32 HWR_GetLightTableID(extracolormap_t *colormap)
{
boolean default_colormap = false;
if (!colormap)
{
colormap = &extra_colormaps[num_extra_colormaps]; // a place to store the hw lighttable id
// alternatively could just store the id in a global variable if there are issues
default_colormap = true;
}
// create hw lighttable if there isn't one
if (!colormap->gl_lighttable_id)
{
UINT8 *colormap_pointer;
if (default_colormap)
colormap_pointer = colormaps; // don't actually use the data from the "default colormap"
else
colormap_pointer = colormap->colormap;
colormap->gl_lighttable_id = HWR_CreateLightTable(colormap_pointer);
}
return colormap->gl_lighttable_id;
}
// Note: all hardware lighttable ids assigned before this
// call become invalid and must not be used.
void HWR_ClearLightTables(void)
{
GL_ClearLightTables();
}
#endif //HWRENDER