// 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 #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); 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(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(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<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(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(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(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(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(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(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(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(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 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 = §ors[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(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(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(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(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(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(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(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(calloc(1, sizeof (*newMipmap))); if (newMipmap == NULL) I_Error("%s: Out of memory", "HWR_GetMappedPatch"); glMipmap->nextcolormap = newMipmap; newMipmap->colormap = static_cast(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(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(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<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(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(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(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