Files
SRB2Kart-Saturn/src/r_draw.cpp
T
Alug 34edd0a2e3 some misc cleanups
also account for a few more double evluations from min/max macros
and save a few angle to fixed conversions in spb handling
2025-11-24 17:44:13 +01:00

690 lines
20 KiB
C++

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2018 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_draw.c
/// \brief span / column drawer functions, for 8bpp and 16bpp
/// All drawing to the view buffer is accomplished in this file.
/// The other refresh files only know about ccordinates,
/// not the architecture of the frame buffer.
/// The frame buffer is a linear one, and we need only the base address.
#include <algorithm>
#include "doomdef.h"
#include "doomstat.h"
#include "r_local.h"
#include "r_things.h"
#include "st_stuff.h" // need ST_HEIGHT
#include "i_video.h"
#include "v_video.h"
#include "m_misc.h"
#include "w_wad.h"
#include "z_zone.h"
#include "console.h" // Until buffering gets finished
#include "k_kart.h" // SRB2kart
#include "i_threads.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif
// ==========================================================================
// COMMON DATA FOR 8bpp AND 16bpp
// ==========================================================================
/** \brief view info
*/
INT32 viewwidth = 0, viewheight = 0, viewwindowx = 0, viewwindowy = 0;
// =========================================================================
// COLUMN DRAWING CODE STUFF
// =========================================================================
drawcolumndata_t g_dc = {};
// -----------------------
// translucency stuff here
// -----------------------
#define NUMTRANSTABLES 9 // how many translucency tables are used
UINT8 *transtables = NULL; // translucency tables
UINT8 *blendtables[NUMBLENDMAPS] = {};
// --------------------------------------------
// c drawer routines
// --------------------------------------------
coldrawfunc_t *colfunc = NULL;
coldrawfunc_t *colfuncs[COLDRAWFUNC_MAX] = {};
int colfunctype = 0;
// =========================================================================
// SPAN DRAWING CODE STUFF
// =========================================================================
drawspandata_t g_ds = {};
// Vectors for Software's tilted slope drawers
floatv3_t *ds_su = NULL, *ds_sv = NULL, *ds_sz = NULL;
float focallengthf = 0.0f;
// For, uh, tilted lighting, duh.
//static INT32 *tiltlighting;
// --------------------------------------------
// c drawer routines
// --------------------------------------------
spandrawfunc_t *spanfunc = NULL;
spandrawfunc_t *spanfuncs[SPANDRAWFUNC_MAX] = {};
// ==========================================================================
// OLD DOOM FUZZY EFFECT
// ==========================================================================
// =========================================================================
// TRANSLATION COLORMAP CODE
// =========================================================================
#define DEFAULT_TT_CACHE_INDEX MAXSKINS
#define BOSS_TT_CACHE_INDEX (MAXSKINS + 1)
#define METALSONIC_TT_CACHE_INDEX (MAXSKINS + 2)
#define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3)
#define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4)
#define BLINK_TT_CACHE_INDEX (MAXSKINS + 5)
#define TT_CACHE_SIZE (MAXSKINS + 6)
#define SKIN_RAMP_LENGTH 16
#define DEFAULT_STARTTRANSCOLOR 160
#define NUM_PALETTE_ENTRIES 256
static UINT8 **translationtablecache[TT_CACHE_SIZE] = {};
static UINT8 **localtranslationtablecache[MAXLOCALSKINS] = {};
CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1] = {};
struct GenerateBlendTables_State
{
RGBA_t *LocalPalette;
RGBA_t *gammaCorrectedPalette;
};
static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state);
static void R_AllocateBlendTables(void);
#ifdef HAVE_THREADS
static void R_GenerateBlendTables_Thread(void *userdata)
{
struct GenerateBlendTables_State *state = static_cast<struct GenerateBlendTables_State *>(userdata);
R_GenerateBlendTables_Core(state);
free(state->LocalPalette);
free(state->gammaCorrectedPalette);
free(state);
}
#endif
/** \brief Initializes the translucency tables used by the Software renderer.
*/
void R_InitTranslucencyTables(void)
{
// Load here the transparency lookup tables 'TRANSx0'
// NOTE: the TRANSx0 resources MUST BE aligned on 64k for the asm
// optimised code (in other words, transtables pointer low word is 0)
transtables = static_cast<UINT8*>(Z_Malloc(NUMTRANSTABLES*0x10000, PU_STATIC, NULL));
W_ReadLump(W_GetNumForName("TRANS10"), transtables);
W_ReadLump(W_GetNumForName("TRANS20"), transtables+0x10000);
W_ReadLump(W_GetNumForName("TRANS30"), transtables+0x20000);
W_ReadLump(W_GetNumForName("TRANS40"), transtables+0x30000);
W_ReadLump(W_GetNumForName("TRANS50"), transtables+0x40000);
W_ReadLump(W_GetNumForName("TRANS60"), transtables+0x50000);
W_ReadLump(W_GetNumForName("TRANS70"), transtables+0x60000);
W_ReadLump(W_GetNumForName("TRANS80"), transtables+0x70000);
W_ReadLump(W_GetNumForName("TRANS90"), transtables+0x80000);
R_AllocateBlendTables();
R_GenerateBlendTables();
}
static colorlookup_t transtab_lut;
static colorlookup_t transtab_lut_corrected;
static void BlendTab_Translucent(UINT8 *table, int style, UINT8 blendamt)
{
INT16 bg, fg;
RGBA_t backrgba, frontrgba, result;
if (table == NULL)
I_Error("BlendTab_Translucent: input table was NULL!");
for (bg = 0; bg < 0x100; bg++)
{
backrgba = pGammaCorrectedPalette[bg];
for (fg = 0; fg < 0x100; fg++)
{
frontrgba = pGammaCorrectedPalette[fg];
#if 0 // perfect implementation
result.rgba = ASTBlendPixel(backrgba, frontrgba, style, 0xFF);
result.rgba = ASTBlendPixel(result, frontrgba, AST_TRANSLUCENT, blendamt);
table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
#else // performance scrabbler
result.rgba = ASTBlendPixel(backrgba, frontrgba, style, 0xFF);
result.rgba = ASTBlendPixel(result, frontrgba, AST_TRANSLUCENT, blendamt);
table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut_corrected, result.s.red, result.s.green, result.s.blue); // pGammaCorrectedPalette
#endif
}
}
}
static void BlendTab_Subtractive(UINT8 *table, int style, UINT8 blendamt)
{
INT16 bg, fg;
if (table == NULL)
I_Error("BlendTab_Subtractive: input table was NULL!");
if (blendamt == 0xFF)
{
memset(table, GetColorLUT(&transtab_lut, 0, 0, 0), 0x10000);
return;
}
for (bg = 0; bg < 0x100; bg++)
{
for (fg = 0; fg < 0x100; fg++)
{
RGBA_t backrgba = V_GetColor(bg);
RGBA_t frontrgba = V_GetColor(fg);
RGBA_t result;
result.rgba = ASTBlendPixel(backrgba, frontrgba, style, 0xFF);
result.s.red = std::max(0, result.s.red - blendamt);
result.s.green = std::max(0, result.s.green - blendamt);
result.s.blue = std::max(0, result.s.blue - blendamt);
table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
}
}
}
static void BlendTab_Modulative(UINT8 *table)
{
INT16 bg, fg;
if (table == NULL)
I_Error("BlendTab_Modulative: input table was NULL!");
for (bg = 0; bg < 0x100; bg++)
{
for (fg = 0; fg < 0x100; fg++)
{
RGBA_t backrgba = V_GetColor(bg);
RGBA_t frontrgba = V_GetColor(fg);
RGBA_t result;
result.rgba = ASTBlendPixel(backrgba, frontrgba, AST_MODULATE, 0);
table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut_corrected, result.s.red, result.s.green, result.s.blue);
}
}
}
static INT32 BlendTab_Count[NUMBLENDMAPS] =
{
NUMTRANSTABLES+1, // blendtab_add
NUMTRANSTABLES+1, // blendtab_subtract
NUMTRANSTABLES+1, // blendtab_reversesubtract
1 // blendtab_modulate
};
static INT32 BlendTab_FromStyle[] =
{
0, // AST_COPY
0, // AST_TRANSLUCENT
blendtab_add, // AST_ADD
blendtab_subtract, // AST_SUBTRACT
blendtab_reversesubtract, // AST_REVERSESUBTRACT
blendtab_modulate, // AST_MODULATE
0 // AST_OVERLAY
};
static void BlendTab_GenerateMaps(INT32 tab, INT32 style, void (*genfunc)(UINT8 *, int, UINT8))
{
INT32 i = 0, num = BlendTab_Count[tab];
const float amtmul = (256.0f / (float)(NUMTRANSTABLES + 1));
for (; i < num; i++)
{
const size_t offs = (0x10000 * i);
const UINT16 alpha = std::fmin(amtmul * i, 0xFF);
genfunc(blendtables[tab] + offs, style, alpha);
}
}
static void R_AllocateBlendTables(void)
{
INT32 i;
for (i = 0; i < NUMBLENDMAPS; i++)
{
blendtables[i] = static_cast<UINT8*>(Z_Malloc(BlendTab_Count[i] * 0x10000, PU_STATIC, NULL));
}
}
void R_GenerateBlendTables(void)
{
#ifdef HAVE_THREADS
// Allocate copies for the worker thread since the originals can be freed in the main thread.
struct GenerateBlendTables_State *state = static_cast<struct GenerateBlendTables_State *>(malloc(sizeof *state));
size_t palsize = 256 * sizeof(RGBA_t);
state->LocalPalette = static_cast<RGBA_t *>(memcpy(malloc(palsize), pLocalPalette, palsize));
state->gammaCorrectedPalette = static_cast<RGBA_t *>(memcpy(malloc(palsize), pGammaCorrectedPalette, palsize));
I_spawn_thread("blend-tables",
R_GenerateBlendTables_Thread, state);
#else
struct GenerateBlendTables_State state = {pLocalPalette, pGammaCorrectedPalette};
R_GenerateBlendTables_Core(&state);
#endif
}
static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state)
{
InitColorLUT(&transtab_lut, state->LocalPalette, false);
InitColorLUT(&transtab_lut_corrected, state->gammaCorrectedPalette, false);
// Additive
BlendTab_GenerateMaps(blendtab_add, AST_ADD, BlendTab_Translucent);
// Subtractive
#if 1
BlendTab_GenerateMaps(blendtab_subtract, AST_SUBTRACT, BlendTab_Subtractive);
#else
BlendTab_GenerateMaps(blendtab_subtract, AST_SUBTRACT, BlendTab_Translucent);
#endif
// Reverse subtractive
BlendTab_GenerateMaps(blendtab_reversesubtract, AST_REVERSESUBTRACT, BlendTab_Translucent);
// Modulative blending only requires a single table
BlendTab_Modulative(blendtables[blendtab_modulate]);
}
#define ClipBlendLevel(style, trans) std::max(std::min((trans), BlendTab_Count[BlendTab_FromStyle[style]]-1), 0)
#define ClipTransLevel(trans) std::max(std::min((trans), NUMTRANSMAPS-2), 0)
UINT8 *R_GetTranslucencyTable(INT32 alphalevel)
{
return transtables + (ClipTransLevel(alphalevel-1) << FF_TRANSSHIFT);
}
UINT8 *R_GetBlendTable(int style, INT32 alphalevel)
{
size_t offs;
if (style <= AST_COPY || style >= AST_OVERLAY)
return transtables + (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
offs = (ClipBlendLevel(style, alphalevel) << FF_TRANSSHIFT);
// Lactozilla: Returns the equivalent to AST_TRANSLUCENT
// if no alpha style matches any of the blend tables.
switch (style)
{
case AST_ADD:
return blendtables[blendtab_add] + offs;
case AST_SUBTRACT:
return blendtables[blendtab_subtract] + offs;
case AST_REVERSESUBTRACT:
return blendtables[blendtab_reversesubtract] + offs;
case AST_MODULATE:
return blendtables[blendtab_modulate];
default:
break;
}
// Return a normal translucency table
if (--alphalevel >= 0)
return transtables + (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
return NULL;
}
boolean R_BlendLevelVisible(INT32 blendmode, INT32 alphalevel)
{
if (blendmode <= AST_COPY || blendmode == AST_SUBTRACT ||
blendmode == AST_MODULATE || blendmode >= AST_OVERLAY)
return true;
return (alphalevel < BlendTab_Count[BlendTab_FromStyle[blendmode]]);
}
/** \brief Retrieves a translation colormap from the cache.
\param skinnum skin number, or a translation mode
\param color translation color
\param flags set GTC_CACHE to use the cache
\return Colormap. If not cached, caller should Z_Free.
*/
static UINT8* RGetTranslationColormap(INT32 skinnum, skincolors_t color, UINT8 flags, boolean local)
{
UINT8 ***tt;
UINT8* ret;
INT32 skintableindex;
if (local)
{
tt = localtranslationtablecache;
skintableindex = skinnum;
}
else
{
tt = translationtablecache;
// Adjust if we want the default colormap
switch (skinnum)
{
case TC_DEFAULT: skintableindex = DEFAULT_TT_CACHE_INDEX; break;
case TC_BOSS: skintableindex = BOSS_TT_CACHE_INDEX; break;
case TC_METALSONIC: skintableindex = METALSONIC_TT_CACHE_INDEX; break;
case TC_ALLWHITE: skintableindex = ALLWHITE_TT_CACHE_INDEX; break;
case TC_RAINBOW: skintableindex = RAINBOW_TT_CACHE_INDEX; break;
case TC_BLINK: skintableindex = BLINK_TT_CACHE_INDEX; break;
default: skintableindex = skinnum; break;
}
}
if (flags & GTC_CACHE)
{
// Allocate table for skin if necessary
if (!tt[skintableindex])
tt[skintableindex] = static_cast<UINT8**>(Z_Calloc(MAXTRANSLATIONS * sizeof(UINT8**), PU_STATIC, NULL));
// Get colormap
ret = tt[skintableindex][color];
}
else
ret = NULL;
// Generate the colormap if necessary
if (!ret)
{
ret = static_cast<UINT8*>(Z_Malloc(NUM_PALETTE_ENTRIES, (flags & GTC_CACHE) ? PU_LEVEL : PU_STATIC, NULL));
K_GenerateKartColormap(ret, skinnum, color, local); //R_GenerateTranslationColormap(ret, skinnum, color); // SRB2kart
// Cache the colormap if desired
if (flags & GTC_CACHE)
tt[skintableindex][color] = ret;
}
return ret;
}
UINT8* R_GetTranslationColormap(INT32 skinnum, skincolors_t color, UINT8 flags)
{
return RGetTranslationColormap(skinnum, color, flags, false);
}
UINT8* R_GetLocalTranslationColormap(skin_t *skin, skin_t *localskin, skincolors_t color, UINT8 flags, boolean local)
{
const INT32 skinnum = localskin ? (localskin - K_GetSkinArray(local)) : (skin - skins);
return RGetTranslationColormap(skinnum, color, flags, (localskin && local));
}
patch_t* R_GetSkinFaceRank(player_t* ply)
{
if (ply->localskin)
{
if (ply->skinlocal)
return localfacerankprefix[ply->localskin - 1];
return facerankprefix[ply->localskin - 1];
}
return facerankprefix[ply->skin];
}
patch_t* R_GetSkinFaceWant(player_t* ply)
{
if (ply->localskin)
{
if (ply->skinlocal)
return localfacewantprefix[ply->localskin - 1];
return facewantprefix[ply->localskin - 1];
}
return facewantprefix[ply->skin];
}
patch_t* R_GetSkinFaceMini(player_t* ply)
{
if (ply->localskin)
{
if (ply->skinlocal)
return localfacemmapprefix[ply->localskin - 1];
return facemmapprefix[ply->localskin - 1];
}
return facemmapprefix[ply->skin];
}
/** \brief Flushes cache of translation colormaps.
Flushes cache of translation colormaps, but doesn't actually free the
colormaps themselves. These are freed when PU_LEVEL blocks are purged,
at or before which point, this function should be called.
\return void
*/
void R_FlushTranslationColormapCache(void)
{
INT32 i;
for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++)
if (translationtablecache[i])
memset(translationtablecache[i], 0, MAXTRANSLATIONS * sizeof(UINT8**));
for (i = 0; i < (INT32)(sizeof(localtranslationtablecache) / sizeof(localtranslationtablecache[0])); i++)
if (localtranslationtablecache[i])
memset(localtranslationtablecache[i], 0, MAXTRANSLATIONS * sizeof(UINT8**));
}
// ==========================================================================
// COMMON DRAWER FOR 8 AND 16 BIT COLOR MODES
// ==========================================================================
// in a perfect world, all routines would be compatible for either mode,
// and optimised enough
//
// in reality, the few routines that can work for either mode, are
// put here
enum columncontext_e columncontext = COLUMNCONTEXT_DIRECT;
enum ColumnFlushType
{
FLUSH_NONE = 0x0000,
FLUSH_OPAQUE = 0x0001,
FLUSH_TRANS = 0x0002,
FLUSH_COLORMAP = 0x0004,
FLUSH_COLORMAP_TRANS = 0x0008,
};
typedef struct drawcolumndata_temp_s
{
INT32 x;
INT32 yl[8], yh[8];
// e6y: resolution limitation is removed
UINT8 *buf;
INT32 startx;
ColumnFlushType type;
INT32 commontop, commonbot;
UINT8 *transmap;
// SoM 7-28-04: Fix the fuzz problem.
UINT8 *translation;
} drawcolumndata_temp_t;
drawcolumndata_temp_t temp_dc = {};
//
// Error functions that will abort if R_FlushColumns tries to flush
// columns without a column type.
//
FUNCNORETURN static ATTRNORETURN void R_FlushWholeError(void)
{
I_Error("R_FlushWholeColumns called without being initialized.\n");
}
FUNCNORETURN static ATTRNORETURN void R_FlushHTError(void)
{
I_Error("R_FlushHTColumns called without being initialized.\n");
}
FUNCNORETURN static ATTRNORETURN void R_QuadFlushError(void)
{
I_Error("R_FlushQuadColumn called without being initialized.\n");
}
static void (*R_FlushWholeColumns)(void) = R_FlushWholeError;
static void (*R_FlushHTColumns)(void) = R_FlushHTError;
static void (*R_FlushQuadColumn)(void) = R_QuadFlushError;
static void R_FlushColumns(void)
{
if (temp_dc.x != 8 || temp_dc.commontop >= temp_dc.commonbot)
R_FlushWholeColumns();
else
{
R_FlushHTColumns();
R_FlushQuadColumn();
}
temp_dc.x = 0;
}
//
// R_ResetColumnBuffer
//
// haleyjd 09/13/04: new function to call from main rendering loop
// which gets rid of the unnecessary reset of various variables during
// column drawing.
//
void R_ResetColumnBuffer(void)
{
// haleyjd 10/06/05: this must not be done if x == 0!
if (temp_dc.x)
{
R_FlushColumns();
}
temp_dc.type = FLUSH_NONE;
R_FlushWholeColumns = R_FlushWholeError;
R_FlushHTColumns = R_FlushHTError;
R_FlushQuadColumn = R_QuadFlushError;
}
/** \brief The R_InitViewBuffer function
Creates lookup tables for getting the framebuffer address
of a pixel to draw.
\param width witdh of buffer
\param height hieght of buffer
\return void
*/
static void R_AllocViewMemory(void)
{
negonearray = static_cast<INT16*>(Z_Realloc(negonearray, sizeof(*negonearray) * viewwidth, PU_STATIC, NULL));
screenheightarray = static_cast<INT16*>(Z_Realloc(screenheightarray, sizeof(*screenheightarray) * viewwidth, PU_STATIC, NULL));
floorclip = static_cast<INT16*>(Z_Realloc(floorclip, sizeof(*floorclip) * viewwidth, PU_STATIC, NULL));
ceilingclip = static_cast<INT16*>(Z_Realloc(ceilingclip, sizeof(*ceilingclip) * viewwidth, PU_STATIC, NULL));
frontscale = static_cast<fixed_t*>(Z_Realloc(frontscale, sizeof(*frontscale) * viewwidth, PU_STATIC, NULL));
xtoviewangle = static_cast<angle_t*>(Z_Realloc(xtoviewangle, sizeof(*xtoviewangle) * (viewwidth + 1), PU_STATIC, NULL));
R_AllocSegMemory();
R_AllocClipSegMemory();
R_AllocPlaneMemory();
#ifdef FLOORSPLATS
R_AllocFloorSpriteTables();
#endif
R_AllocVisSpriteMemory();
}
void R_InitViewBuffer(void)
{
R_AllocViewMemory();
viewwindowx = 0;
viewwindowy = 0;
INT32 bufsize = (vid.width * 8) * sizeof(*temp_dc.buf);
if (temp_dc.buf)
{
#if defined(__SSE__)
aligned_free(temp_dc.buf);
#else
Z_Free(temp_dc.buf);
#endif
}
memset(&temp_dc, 0, sizeof(temp_dc));
#if defined(__SSE__)
while (bufsize & 15)
bufsize++;
temp_dc.buf = static_cast<UINT8*>(aligned_alloc(16, bufsize));
#else
temp_dc.buf = static_cast<UINT8*>(Z_Malloc(bufsize, PU_STATIC, NULL));
#endif
memset(temp_dc.buf, 0, bufsize);
}
/** \brief The R_VideoErase function
Copy a screen buffer.
\param ofs offest from buffer
\param count bytes to erase
\return void
*/
void R_VideoErase(size_t ofs, INT32 count)
{
// LFB copy.
// This might not be a good idea if memcpy
// is not optimal, e.g. byte by byte on
// a 32bit CPU, as GNU GCC/Linux libc did
// at one point.
memcpy(vid.screens[0] + ofs, vid.screens[1] + ofs, count);
}
// ==========================================================================
// INCLUDE DRAWING CODE HERE
// ==========================================================================
#include "r_draw_column.cpp"
#include "r_draw_span.cpp"