Files
SRB2Kart-Saturn/src/r_patch.cpp
T
Alug 6a9aea4dd1 use std::vector for our image buffers
this is just to have it cleaner and less code bloat
the resizing loop will almost never trigger, 1meg is enough to even rotate gigantamax eevee without needing to grow
2025-10-13 19:20:40 +02:00

806 lines
20 KiB
C++

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
// Copyright (C) 2019-2020 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 r_patch.c
/// \brief Patch generation.
#include "byteptr.h"
#include "doomdef.h"
#include "r_patch.h"
#include "r_things.h"
#include "r_skins.h"
#include "z_zone.h"
#include "w_wad.h"
#include <vector>
#ifdef HWRENDER
#include "hardware/hw_glob.h"
#endif
//
// R_ParseSpriteInfoFrame
//
// Parse a SPRTINFO frame.
//
static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
{
char *sprinfoToken;
size_t sprinfoTokenLength;
char *frameChar = NULL;
UINT8 frameFrame = 0xFF;
INT16 frameXPivot = 0;
INT16 frameYPivot = 0;
rotaxis_t frameRotAxis = ROTAXIS_X;
// Sprite identifier
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
}
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 1)
{
I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
}
else
frameChar = sprinfoToken;
frameFrame = R_Char2Frame(frameChar[0]);
Z_Free(sprinfoToken);
// Left Curly Brace
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
I_Error("Error parsing SPRTINFO lump: Missing sprite info");
else
{
if (strcmp(sprinfoToken,"{")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
}
while (strcmp(sprinfoToken,"}")!=0)
{
if (stricmp(sprinfoToken, "XPIVOT")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
frameXPivot = atoi(sprinfoToken);
}
else if (stricmp(sprinfoToken, "YPIVOT")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
frameYPivot = atoi(sprinfoToken);
}
else if (stricmp(sprinfoToken, "ROTAXIS")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0))
frameRotAxis = ROTAXIS_X;
else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0))
frameRotAxis = ROTAXIS_Y;
else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0))
frameRotAxis = ROTAXIS_Z;
}
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
}
}
}
Z_Free(sprinfoToken);
}
// set fields
info->pivot[frameFrame].x = frameXPivot;
info->pivot[frameFrame].y = frameYPivot;
info->pivot[frameFrame].rotaxis = frameRotAxis;
}
//
// R_ParseSpriteInfo
//
// Parse a SPRTINFO lump.
//
static void R_ParseSpriteInfo(boolean spr2)
{
spriteinfo_t *info;
char *sprinfoToken;
size_t sprinfoTokenLength;
char newSpriteName[5]; // no longer dynamically allocated
spritenum_t sprnum = NUMSPRITES;
//playersprite_t spr2num = NUMPLAYERSPRITES;
INT32 i;
INT32 skinnumbers[MAXSKINS]; // >variable 'skinnumbers' set but not used >compile error when commented out
INT32 foundskins = 0;
// Sprite name
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
}
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 4)
{
I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken);
}
else
{
memset(&newSpriteName, 0, 5);
M_Memcpy(newSpriteName, sprinfoToken, sprinfoTokenLength);
// ^^ we've confirmed that the token is == 4 characters so it will never overflow a 5 byte char buffer
strupr(newSpriteName); // Just do this now so we don't have to worry about it
}
Z_Free(sprinfoToken);
if (!spr2)
{
for (i = 0; i <= NUMSPRITES; i++)
{
if (i == NUMSPRITES)
I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
if (!memcmp(newSpriteName,sprnames[i],4))
{
sprnum = static_cast<spritenum_t>(i);
break;
}
}
}
// allocate a spriteinfo
info = static_cast<spriteinfo_t*>(Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL));
info->available = true;
// Left Curly Brace
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
}
if (strcmp(sprinfoToken,"{")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
}
while (strcmp(sprinfoToken,"}")!=0)
{
if (stricmp(sprinfoToken, "SKIN")==0)
{
INT32 skinnum;
char *skinName = NULL;
if (!spr2)
I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");
Z_Free(sprinfoToken);
// Skin name
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be");
}
// copy skin name yada yada
sprinfoTokenLength = strlen(sprinfoToken);
skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL);
M_Memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char));
skinName[sprinfoTokenLength] = '\0';
strlwr(skinName);
Z_Free(sprinfoToken);
skinnum = R_AnySkinAvailable(skinName);
if (skinnum == -1)
I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);
skinnumbers[foundskins] = skinnum;
foundskins++;
}
else if (stricmp(sprinfoToken, "FRAME")==0)
{
R_ParseSpriteInfoFrame(info);
Z_Free(sprinfoToken);
if (spr2)
{
if (!foundskins)
I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
for (i = 0; i < foundskins; i++)
{
size_t skinnum = skinnumbers[i];
skin_t *skin;
if (allskins[skinnum].localskin)
skin = &localskins[allskins[skinnum].localnum];
else
skin = &skins[allskins[skinnum].localnum];
M_Memcpy(&skin->sprinfo, info, sizeof(spriteinfo_t));
}
}
else
M_Memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t));
}
else
{
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
}
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
}
}
}
else
{
I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
}
Z_Free(sprinfoToken);
Z_Free(info);
}
//
// R_ParseSPRTINFOLump
//
// Read a SPRTINFO lump.
//
void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
{
char *sprinfoLump;
size_t sprinfoLumpLength;
char *sprinfoText;
char *sprinfoToken;
// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
// need to make a space of memory where I can ensure that it will terminate
// correctly. Start by loading the relevant data from the WAD.
sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
// If that didn't exist, we have nothing to do here.
if (sprinfoLump == NULL) return;
// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL);
// Now move the contents of the lump into this new location.
memmove(sprinfoText,sprinfoLump,sprinfoLumpLength);
// Make damn well sure the last character in our new memory location is \0.
sprinfoText[sprinfoLumpLength] = '\0';
// Finally, free up the memory from the first data load, because we really
// don't need it.
Z_Free(sprinfoLump);
sprinfoToken = M_GetToken(sprinfoText);
while (sprinfoToken != NULL)
{
CONS_Printf("Attempting to parse le SPRTINFO lump for ya..\n");
if (!stricmp(sprinfoToken, "SPRITE"))
R_ParseSpriteInfo(false);
else if (!stricmp(sprinfoToken, "SPRITE2"))
R_ParseSpriteInfo(true);
else
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
}
Z_Free((void *)sprinfoText);
}
//
// R_LoadSpriteInfoLumps
//
// Load and read every SPRTINFO lump from the specified file.
//
void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
{
lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
UINT16 i;
char *name;
for (i = 0; i < numlumps; i++, lumpinfo++)
{
name = lumpinfo->name;
// Load SPRTINFO and SPR_ lumps as SpriteInfo
if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
R_ParseSPRTINFOLump(wadnum, i);
}
}
//
// Creates a patch.
// Assumes a PU_PATCH zone memory tag and no user, but can always be set later
//
patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest)
{
patch_t *patch = static_cast<patch_t*>((dest == NULL) ? Z_Calloc(sizeof(patch_t), PU_PATCH, NULL) : dest);
if (source)
{
INT32 col, colsize;
size_t size = sizeof(INT32) * SHORT(source->width);
size_t offs = (sizeof(INT16) * 4) + size;
patch->width = SHORT(source->width);
patch->height = SHORT(source->height);
patch->leftoffset = SHORT(source->leftoffset);
patch->topoffset = SHORT(source->topoffset);
patch->columnofs = static_cast<INT32*>(Z_Calloc(size, PU_PATCH_DATA, NULL));
for (col = 0; col < source->width; col++)
{
// This makes the column offsets relative to the column data itself,
// instead of the entire patch data
patch->columnofs[col] = LONG(source->columnofs[col]) - offs;
}
if (!srcsize)
I_Error("Patch_Create: no source size!");
colsize = (INT32)(srcsize) - (INT32)offs;
if (colsize <= 0)
I_Error("Patch_Create: no column data!");
patch->columns = static_cast<UINT8*>(Z_Calloc(colsize, PU_PATCH_DATA, NULL));
M_Memcpy(patch->columns, ((UINT8 *)source + LONG(source->columnofs[0])), colsize);
}
return patch;
}
//
// Frees a patch from memory.
//
static void Patch_FreeData(patch_t *patch)
{
#ifdef HWRENDER
if (patch->hardware)
HWR_FreeTexture(patch);
#endif
#ifdef ROTSPRITE
/*if (patch->rotated)
{
rotsprite_t *rotsprite = patch->rotated;
INT32 i = 0;
for (; i < rotsprite->angles; i++)
{
if (rotsprite->patches[i])
Patch_Free(rotsprite->patches[i]);
}
Z_Free(rotsprite->patches);
Z_Free(rotsprite);
}*/
#endif
if (patch->columnofs)
Z_Free(patch->columnofs);
if (patch->columns)
Z_Free(patch->columns);
}
void Patch_Free(patch_t *patch)
{
Patch_FreeData(patch);
Z_Free(patch);
}
//
// Frees patches with a tag range.
//
static boolean Patch_FreeTagsCallback(void *mem)
{
patch_t *patch = (patch_t *)mem;
Patch_FreeData(patch);
return true;
}
void Patch_FreeTags(INT32 lowtag, INT32 hightag)
{
Z_IterateTags(lowtag, hightag, Patch_FreeTagsCallback);
}
#ifdef HWRENDER
//
// Allocates a hardware patch.
//
void *Patch_AllocateHardwarePatch(patch_t *patch)
{
if (!patch->hardware)
{
GLPatch_t *glPatch = static_cast<GLPatch_t*>(Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, &patch->hardware));
glPatch->mipmap = static_cast<GLMipmap_t*>(Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, &glPatch->mipmap));
}
return (void *)(patch->hardware);
}
//
// Creates a hardware patch.
//
void *Patch_CreateGL(patch_t *patch)
{
GLPatch_t *glPatch = (GLPatch_t *)Patch_AllocateHardwarePatch(patch);
if (!glPatch->mipmap->data) // Run HWR_MakePatch in all cases, to recalculate some things
HWR_MakePatch(patch, glPatch, glPatch->mipmap, false);
return glPatch;
}
#endif // HWRENDER
// 1 MB should be enough for most cases
static std::vector<UINT8> imgbuf(1024 * 1024);
//
// R_MaskedFlatToPatch
//
// Convert raw pixels to a patch.
//
void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize)
{
INT16 x, y;
UINT8 *img, *imgptr;
UINT8 *colpointers, *startofspan;
size_t size = 0;
if (!raw)
return NULL;
// Allocate a staging buffer with the maximum size needed for a patch of the same size as the input.
// round up to nearest multiple of 254-pixel posts, plus 1 more 254-pixel post for paranoia reasons
size_t maxcolumnsize = (2 + (height - 1) / 256) * 256;
// the patch header, and width columns of the max column size
size_t maxoutsize = maxcolumnsize * width + (8 + 4 * width);
// so, a 512x512 flat should maximally need 393,760 (384.53 KiB) bytes.
// quite a bit smaller than 64 megabytes, and much less annoying to the windows debug allocator!
while (imgbuf.size() < maxoutsize)
{
imgbuf.resize(imgbuf.size() * 2);
}
imgptr = imgbuf.data();
// Write image size and offset
WRITEINT16(imgptr, width);
WRITEINT16(imgptr, height);
WRITEINT16(imgptr, leftoffset);
WRITEINT16(imgptr, topoffset);
// Leave placeholder to column pointers
colpointers = imgptr;
imgptr += width*4;
// Write columns
for (x = 0; x < width; x++)
{
int lastStartY = 0;
int spanSize = 0;
startofspan = NULL;
// Write column pointer
WRITEINT32(colpointers, imgptr - imgbuf.data());
// Write pixels
for (y = 0; y < height; y++)
{
UINT8 pixel = raw[((y * width) + x)];
UINT8 opaque = (pixel != 0xf7); // If not 247 (0xf7), we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITEUINT8(imgptr, 0);
startofspan = NULL;
continue;
}
// Start new column if we need to
if (!startofspan || spanSize == 255)
{
int writeY = y;
// If we reached the span size limit, finish the previous span
if (startofspan)
WRITEUINT8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITEUINT8(imgptr, writeY);
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITEUINT8(imgptr, pixel);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITEUINT8(imgptr, 0);
WRITEUINT8(imgptr, 0xFF);
}
size = imgptr-imgbuf.data();
img = static_cast<UINT8*>(Z_Malloc(size, PU_STATIC, NULL));
memcpy(img, imgbuf.data(), size);
if (destsize != NULL)
*destsize = size;
patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
#ifdef HWRENDER
Patch_CreateGL(converted);
#endif
Z_Free(img);
return converted;
}
//
// R_MaskedFlatToPatch
//
// Convert a masked flat to a patch.
// Explanation of "masked" flats in R_PatchToMaskedFlat.
//
void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize)
{
INT16 x, y;
UINT8 *img, *imgptr;
UINT8 *colpointers, *startofspan;
size_t size = 0;
if (!raw)
return NULL;
// Allocate a staging buffer with the maximum size needed for a patch of the same size as the input.
// round up to nearest multiple of 254-pixel posts, plus 1 more 254-pixel post for paranoia reasons
size_t maxcolumnsize = (2 + (height - 1) / 256) * 256;
// the patch header, and width columns of the max column size
size_t maxoutsize = maxcolumnsize * width + (8 + 4 * width);
// so, a 512x512 flat should maximally need 393,760 (384.53 KiB) bytes.
// quite a bit smaller than 64 megabytes, and much less annoying to the windows debug allocator!
while (imgbuf.size() < maxoutsize)
{
imgbuf.resize(imgbuf.size() * 2);
}
imgptr = imgbuf.data();
// Write image size and offset
WRITEINT16(imgptr, width);
WRITEINT16(imgptr, height);
WRITEINT16(imgptr, leftoffset);
WRITEINT16(imgptr, topoffset);
// Leave placeholder to column pointers
colpointers = imgptr;
imgptr += width*4;
// Write columns
for (x = 0; x < width; x++)
{
int lastStartY = 0;
int spanSize = 0;
startofspan = NULL;
// Write column pointer
WRITEINT32(colpointers, imgptr - imgbuf.data());
// Write pixels
for (y = 0; y < height; y++)
{
UINT16 pixel = raw[((y * width) + x)];
UINT8 paletteIndex = (pixel & 0xFF);
UINT8 opaque = (pixel != 0xFF00); // If 1, we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITEUINT8(imgptr, 0);
startofspan = NULL;
continue;
}
// Start new column if we need to
if (!startofspan || spanSize == 255)
{
int writeY = y;
// If we reached the span size limit, finish the previous span
if (startofspan)
WRITEUINT8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITEUINT8(imgptr, writeY);
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITEUINT8(imgptr, paletteIndex);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITEUINT8(imgptr, 0);
WRITEUINT8(imgptr, 0xFF);
}
size = imgptr-imgbuf.data();
img = static_cast<UINT8*>(Z_Malloc(size, PU_STATIC, NULL));
memcpy(img, imgbuf.data(), size);
if (destsize != NULL)
*destsize = size;
patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
#ifdef HWRENDER
Patch_CreateGL(converted);
#endif
Z_Free(img);
return converted;
}
UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
{
fixed_t ofs;
column_t *column;
UINT8 *source = NULL;
INT16 width;
if (patch == NULL)
I_Error("GetPatchPixel: patch == NULL");
width = patch->width;
if (x >= 0 && x < width)
{
INT32 colx = flip ? (width-1)-x : x;
INT32 topdelta, prevdelta = -1;
column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[colx]));
while (column->topdelta != 0xff)
{
topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
ofs = (y - topdelta);
if (y >= topdelta && ofs < column->length)
{
source = (UINT8 *)(column) + 3;
return source[ofs];
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
return 0xFF00;
}
void R_PatchToPixels(patch_t *patch, UINT8 *dst)
{
const INT16 width = patch->width;
// init out destination to cyan so we can easily check for it when we convert it back to a patch
memset(dst, 0xf7, (width * patch->height)); // should this be here or should this be taken care of before passing it?
for (INT32 x = 0; x < width; ++x)
{
INT32 y = 0;
INT32 prevdelta = -1;
column_t *column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[x]));
while (column->topdelta != 0xff)
{
INT32 topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
for (; y-topdelta < column->length; ++y)
{
UINT8 pixel = 0;
if (y >= topdelta)
{
UINT8 *source = (UINT8*)(column) + 3;
pixel = source[y-topdelta];
}
dst[(y*width) + x] = pixel;
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
}