// BLANKART //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-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 v_video.c /// \brief Gamma correction LUT stuff /// Functions to draw patches (by post) directly to screen. /// Functions to blit a block to the screen. #include "doomdef.h" #include "doomtype.h" #include "d_main.h" #include "r_local.h" #include "p_local.h" // stplyr #include "g_game.h" // players #include "v_video.h" #include "st_stuff.h" #include "hu_stuff.h" #include "f_finale.h" #include "r_draw.h" #include "console.h" #include "i_video.h" // rendermode #include "z_zone.h" #include "m_misc.h" #include "m_random.h" #include "doomstat.h" #include "r_fps.h" #include "qs22j.h" #ifdef HWRENDER #include "hardware/hw_glob.h" #endif // SRB2Kart #include "k_hud.h" #include "k_boss.h" #include "i_time.h" static CV_PossibleValue_t fps_cons_t[] = {{0, "No"}, {1, "Normal"}, {2, "Compact"}, {3, "Old"}, {4, "Old Compact"}, {0, NULL}}; consvar_t cv_ticrate = CVAR_INIT ("showfps", "No", CV_SAVE, fps_cons_t, NULL); static void CV_palette_OnChange(void); #ifdef BACKWARDSCOMPATCORRECTION static CV_PossibleValue_t gamma_cons_t[] = {{-15, "MIN"}, {5, "MAX"}, {0, NULL}}; consvar_t cv_globalgamma = CVAR_INIT ("gamma", "0", CV_SAVE|CV_CALL|CV_NOINIT, gamma_cons_t, CV_palette_OnChange); #endif static CV_PossibleValue_t brightness_cons_t[] = {{-15, "MIN"}, {5, "MAX"}, {0, NULL}}; consvar_t cv_globalbrightness = CVAR_INIT ("brightness", "0", CV_SAVE|CV_CALL, brightness_cons_t, CV_palette_OnChange); static CV_PossibleValue_t saturation_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; consvar_t cv_globalsaturation = CVAR_INIT ("saturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); #define huecoloursteps 4 static CV_PossibleValue_t hue_cons_t[] = {{0, "MIN"}, {(huecoloursteps*6)-1, "MAX"}, {0, NULL}}; consvar_t cv_rhue = CVAR_INIT ("rhue", "0", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_yhue = CVAR_INIT ("yhue", "4", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_ghue = CVAR_INIT ("ghue", "8", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_chue = CVAR_INIT ("chue", "12", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_bhue = CVAR_INIT ("bhue", "16", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_mhue = CVAR_INIT ("mhue", "20", CV_SAVE|CV_CALL|CV_NOINIT, hue_cons_t, CV_palette_OnChange); consvar_t cv_rbrightness = CVAR_INIT ("rbrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_ybrightness = CVAR_INIT ("ybrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_gbrightness = CVAR_INIT ("gbrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_cbrightness = CVAR_INIT ("cbrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_bbrightness = CVAR_INIT ("bbrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_mbrightness = CVAR_INIT ("mbrightness", "0", CV_SAVE|CV_CALL|CV_NOINIT, brightness_cons_t, CV_palette_OnChange); consvar_t cv_rsaturation = CVAR_INIT ("rsaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); consvar_t cv_ysaturation = CVAR_INIT ("ysaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); consvar_t cv_gsaturation = CVAR_INIT ("gsaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); consvar_t cv_csaturation = CVAR_INIT ("csaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); consvar_t cv_bsaturation = CVAR_INIT ("bsaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); consvar_t cv_msaturation = CVAR_INIT ("msaturation", "10", CV_SAVE|CV_CALL|CV_NOINIT, saturation_cons_t, CV_palette_OnChange); static CV_PossibleValue_t constextsize_cons_t[] = { {V_NOSCALEPATCH, "Small"}, {V_SMALLSCALEPATCH, "Medium"}, {V_MEDSCALEPATCH, "Large"}, {0, "Huge"}, {0, NULL}}; static void CV_constextsize_OnChange(void); consvar_t cv_constextsize = CVAR_INIT ("con_textsize", "Medium", CV_SAVE|CV_CALL, constextsize_cons_t, CV_constextsize_OnChange); consvar_t cv_palette = CVAR_INIT ("palette", "", CV_CHEAT|CV_CALL|CV_NOINIT, NULL, CV_palette_OnChange); consvar_t cv_palettenum = CVAR_INIT ("palettenum", "0", CV_CHEAT|CV_CALL|CV_NOINIT, CV_Unsigned, CV_palette_OnChange); // local copy of the palette for V_GetColor() RGBA_t *pLocalPalette = NULL; RGBA_t *pMasterPalette = NULL; RGBA_t *pGammaCorrectedPalette = NULL; static size_t currentPaletteSize; /* The following was an extremely helpful resource when developing my Colour Cube LUT. http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html Please check it out if you're trying to maintain this. toast 18/04/17 */ float Cubepal[2][2][2][3]; boolean Cubeapply = false; // returns whether to apply cube, selectively avoiding expensive operations static boolean InitCube(void) { boolean apply = false; UINT8 q; float working[2][2][2][3] = // the initial positions of the corners of the colour cube! { { { {0.0, 0.0, 0.0}, // black corner {0.0, 0.0, 1.0} // blue corner }, { {0.0, 1.0, 0.0}, // green corner {0.0, 1.0, 1.0} // cyan corner } }, { { {1.0, 0.0, 0.0}, // red corner {1.0, 0.0, 1.0} // magenta corner }, { {1.0, 1.0, 0.0}, // yellow corner {1.0, 1.0, 1.0} // white corner } } }; float desatur[3]; // grey float globalgammamul, globalgammaoffs; boolean doinggamma; if (loaded_config == false) return false; #define diffcons(cv) (cv.value != atoi(cv.defaultvalue)) doinggamma = diffcons(cv_globalbrightness); #define gammascale 8 globalgammamul = (cv_globalbrightness.value ? ((255.0f - (gammascale*abs(cv_globalbrightness.value))) / 255.0f) : 1.0f); globalgammaoffs = ((cv_globalbrightness.value > 0) ? ((gammascale*cv_globalbrightness.value) / 255.0f) : 0.0f); desatur[0] = desatur[1] = desatur[2] = globalgammaoffs + (0.33*globalgammamul); if (doinggamma || diffcons(cv_rhue) || diffcons(cv_yhue) || diffcons(cv_ghue) || diffcons(cv_chue) || diffcons(cv_bhue) || diffcons(cv_mhue) || diffcons(cv_rbrightness) || diffcons(cv_ybrightness) || diffcons(cv_gbrightness) || diffcons(cv_cbrightness) || diffcons(cv_bbrightness) || diffcons(cv_mbrightness)) // set the gamma'd/hued positions (saturation is done later) { float mod, tempgammamul, tempgammaoffs; apply = true; working[0][0][0][0] = working[0][0][0][1] = working[0][0][0][2] = globalgammaoffs; working[1][1][1][0] = working[1][1][1][1] = working[1][1][1][2] = globalgammaoffs+globalgammamul; #define dohue(hue, gamma, loc) \ tempgammamul = (gamma ? ((255 - (gammascale*abs(gamma)))/255.0)*globalgammamul : globalgammamul);\ tempgammaoffs = ((gamma > 0) ? ((gammascale*gamma)/255.0) + globalgammaoffs : globalgammaoffs);\ mod = ((hue % huecoloursteps)*(tempgammamul)/huecoloursteps);\ switch (hue/huecoloursteps)\ {\ case 0:\ default:\ loc[0] = tempgammaoffs+tempgammamul;\ loc[1] = tempgammaoffs+mod;\ loc[2] = tempgammaoffs;\ break;\ case 1:\ loc[0] = tempgammaoffs+tempgammamul-mod;\ loc[1] = tempgammaoffs+tempgammamul;\ loc[2] = tempgammaoffs;\ break;\ case 2:\ loc[0] = tempgammaoffs;\ loc[1] = tempgammaoffs+tempgammamul;\ loc[2] = tempgammaoffs+mod;\ break;\ case 3:\ loc[0] = tempgammaoffs;\ loc[1] = tempgammaoffs+tempgammamul-mod;\ loc[2] = tempgammaoffs+tempgammamul;\ break;\ case 4:\ loc[0] = tempgammaoffs+mod;\ loc[1] = tempgammaoffs;\ loc[2] = tempgammaoffs+tempgammamul;\ break;\ case 5:\ loc[0] = tempgammaoffs+tempgammamul;\ loc[1] = tempgammaoffs;\ loc[2] = tempgammaoffs+tempgammamul-mod;\ break;\ } dohue(cv_rhue.value, cv_rbrightness.value, working[1][0][0]); dohue(cv_yhue.value, cv_ybrightness.value, working[1][1][0]); dohue(cv_ghue.value, cv_gbrightness.value, working[0][1][0]); dohue(cv_chue.value, cv_cbrightness.value, working[0][1][1]); dohue(cv_bhue.value, cv_bbrightness.value, working[0][0][1]); dohue(cv_mhue.value, cv_mbrightness.value, working[1][0][1]); #undef dohue } #define dosaturation(a, e) a = ((1 - work)*e + work*a) #define docvsat(cv_sat, hue, gamma, r, g, b) \ if diffcons(cv_sat)\ {\ float work, mod, tempgammamul, tempgammaoffs;\ apply = true;\ work = (cv_sat.value/10.0);\ mod = ((hue % huecoloursteps)*(1.0)/huecoloursteps);\ if (hue & huecoloursteps)\ mod = 2-mod;\ else\ mod += 1;\ tempgammamul = (gamma ? ((255 - (gammascale*abs(gamma)))/255.0)*globalgammamul : globalgammamul);\ tempgammaoffs = ((gamma > 0) ? ((gammascale*gamma)/255.0) + globalgammaoffs : globalgammaoffs);\ for (q = 0; q < 3; q++)\ dosaturation(working[r][g][b][q], (tempgammaoffs+(desatur[q]*mod*tempgammamul)));\ } docvsat(cv_rsaturation, cv_rhue.value, cv_rbrightness.value, 1, 0, 0); docvsat(cv_ysaturation, cv_yhue.value, cv_ybrightness.value, 1, 1, 0); docvsat(cv_gsaturation, cv_ghue.value, cv_gbrightness.value, 0, 1, 0); docvsat(cv_csaturation, cv_chue.value, cv_cbrightness.value, 0, 1, 1); docvsat(cv_bsaturation, cv_bhue.value, cv_bbrightness.value, 0, 0, 1); docvsat(cv_msaturation, cv_mhue.value, cv_mbrightness.value, 1, 0, 1); #undef gammascale if diffcons(cv_globalsaturation) { float work = (cv_globalsaturation.value/10.0); apply = true; for (q = 0; q < 3; q++) { dosaturation(working[1][0][0][q], desatur[q]); dosaturation(working[0][1][0][q], desatur[q]); dosaturation(working[0][0][1][q], desatur[q]); dosaturation(working[1][1][0][q], 2*desatur[q]); dosaturation(working[0][1][1][q], 2*desatur[q]); dosaturation(working[1][0][1][q], 2*desatur[q]); } } #undef dosaturation #undef diffcons if (!apply) return false; #define dowork(i, j, k, l) \ if (working[i][j][k][l] > 1.0)\ working[i][j][k][l] = 1.0;\ else if (working[i][j][k][l] < 0.0)\ working[i][j][k][l] = 0.0;\ Cubepal[i][j][k][l] = working[i][j][k][l] for (q = 0; q < 3; q++) { dowork(0, 0, 0, q); dowork(1, 0, 0, q); dowork(0, 1, 0, q); dowork(1, 1, 0, q); dowork(0, 0, 1, q); dowork(1, 0, 1, q); dowork(0, 1, 1, q); dowork(1, 1, 1, q); } #undef dowork return true; } UINT32 V_GammaCorrect(UINT32 input, double power) { RGBA_t result; double linear; result.rgba = input; linear = ((double)result.s.red)/255.0f; linear = pow(linear, power)*255.0f; result.s.red = (UINT8)(linear); linear = ((double)result.s.green)/255.0f; linear = pow(linear, power)*255.0f; result.s.green = (UINT8)(linear); linear = ((double)result.s.blue)/255.0f; linear = pow(linear, power)*255.0f; result.s.blue = (UINT8)(linear); return result.rgba; } // keep a copy of the palette so that we can get the RGB value for a color index at any time. static void LoadPalette(const char *lumpname) { lumpnum_t lumpnum = W_GetNumForName(lumpname); size_t i, palsize; UINT8 *pal; currentPaletteSize = W_LumpLength(lumpnum); palsize = currentPaletteSize / 3; Cubeapply = InitCube(); if (pLocalPalette != pMasterPalette) Z_Free(pLocalPalette); Z_Free(pMasterPalette); Z_Free(pGammaCorrectedPalette); pMasterPalette = Z_Malloc(sizeof (*pMasterPalette)*palsize, PU_STATIC, NULL); if (Cubeapply) pLocalPalette = Z_Malloc(sizeof (*pLocalPalette)*palsize, PU_STATIC, NULL); else pLocalPalette = pMasterPalette; pGammaCorrectedPalette = Z_Malloc(sizeof (*pGammaCorrectedPalette)*palsize, PU_STATIC, NULL); pal = W_CacheLumpNum(lumpnum, PU_CACHE); for (i = 0; i < palsize; i++) { pMasterPalette[i].s.red = *pal++; pMasterPalette[i].s.green = *pal++; pMasterPalette[i].s.blue = *pal++; pMasterPalette[i].s.alpha = 0xFF; } // remap the palette from 2.1 to 2.2 indices in compatmode RGBA_t *palcopy = NULL; if (wadfiles[WADFILENUM(lumpnum)]->compatmode) { palcopy = malloc(sizeof(*palcopy)*palsize); memcpy(palcopy, pMasterPalette, sizeof(*palcopy)*palsize); } for (i = 0; i < palsize; i++) { if (palcopy) pMasterPalette[i].rgba = palcopy[R_InvPaletteRemap(i)].rgba; pGammaCorrectedPalette[i].rgba = V_GammaDecode(pMasterPalette[i].rgba); if (!Cubeapply) continue; // Short hand this so its easier to type RGBA_t pGCP = pGammaCorrectedPalette[i]; V_CubeApply(&pGCP); pLocalPalette[i].rgba = V_GammaEncode(pGCP.rgba); } if (palcopy) free(palcopy); } void V_CubeApply(RGBA_t *input) { float working[4][3]; float linear; UINT8 q; if (!Cubeapply) return; linear = ((*input).s.red/255.0); #define dolerp(e1, e2) ((1 - linear)*e1 + linear*e2) for (q = 0; q < 3; q++) { working[0][q] = dolerp(Cubepal[0][0][0][q], Cubepal[1][0][0][q]); working[1][q] = dolerp(Cubepal[0][1][0][q], Cubepal[1][1][0][q]); working[2][q] = dolerp(Cubepal[0][0][1][q], Cubepal[1][0][1][q]); working[3][q] = dolerp(Cubepal[0][1][1][q], Cubepal[1][1][1][q]); } linear = ((*input).s.green/255.0); for (q = 0; q < 3; q++) { working[0][q] = dolerp(working[0][q], working[1][q]); working[1][q] = dolerp(working[2][q], working[3][q]); } linear = ((*input).s.blue/255.0); for (q = 0; q < 3; q++) { working[0][q] = 255*dolerp(working[0][q], working[1][q]); if (working[0][q] > 255.0f) working[0][q] = 255.0f; else if (working[0][q] < 0.0f) working[0][q] = 0.0; } #undef dolerp (*input).s.red = (UINT8)(working[0][0]); (*input).s.green = (UINT8)(working[0][1]); (*input).s.blue = (UINT8)(working[0][2]); } const char *R_GetPalname(UINT16 num) { static char palname[9]; char newpal[9] = "PLAYPAL"; if (num > 0 && num <= 10000) snprintf(newpal, 8, "PAL%04u", num-1); strncpy(palname, newpal, 8); return palname; } const char *GetPalette(void) { const char *user = cv_palette.string; if (user && user[0]) { if (W_CheckNumForName(user) == LUMPERROR) { CONS_Alert(CONS_WARNING, "cv_palette %s lump does not exist\n", user); } else { return cv_palette.string; } } if (gamestate == GS_LEVEL) return R_GetPalname((encoremode ? mapheaderinfo[gamemap-1]->encorepal : mapheaderinfo[gamemap-1]->palette)); return "PLAYPAL"; } void V_ReloadPalette(void) { LoadPalette(GetPalette()); } // -------------+ // V_SetPalette : Set the current palette to use for palettized graphics // : // -------------+ void V_SetPalette(INT32 palettenum) { if (!pLocalPalette) V_ReloadPalette(); if (palettenum == 0) { palettenum = cv_palettenum.value; if (palettenum * 256U > currentPaletteSize - 256) { CONS_Alert(CONS_WARNING, "cv_palettenum %d out of range\n", palettenum); palettenum = 0; } } #ifdef HWRENDER if (rendermode == render_opengl) HWR_SetPalette(&pLocalPalette[palettenum*256]); #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) else #endif #endif if (rendermode != render_none) I_SetPalette(&pLocalPalette[palettenum*256]); } void V_SetPaletteLump(const char *pal) { LoadPalette(pal); V_SetPalette(0); } static void CV_palette_OnChange(void) { if (loaded_config == false) return; // recalculate Color Cube V_ReloadPalette(); V_SetPalette(0); } static void CV_constextsize_OnChange(void) { if (!con_startup) con_recalc = true; } // -------------------------------------------------------------------------- // Copy a rectangular area from one bitmap to another (8bpp) // -------------------------------------------------------------------------- void VID_BlitLinearScreen(const UINT8 *restrict srcptr, UINT8 *restrict destptr, INT32 width, INT32 height, size_t srcrowbytes, size_t destrowbytes) { if (srcrowbytes == destrowbytes && srcrowbytes == (size_t)width) { size_t i = srcrowbytes * height; #if defined(__SSE__) while (i >= 16) { // TODO: find where the buffer is misaligned at times and align it _mm_storeu_ps((void *)destptr, _mm_loadu_ps((const void *)srcptr)); srcptr += 16; destptr += 16; i -= 16; } #endif memcpy(destptr, srcptr, i); } else { while (height--) { memcpy(destptr, srcptr, width); destptr += destrowbytes; srcptr += srcrowbytes; } } } void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dup) { // dup adjustments pretend that screen width is BASEVIDWIDTH * dup INT32 screenwidth = vid.width; INT32 screenheight = vid.height; INT32 basewidth = BASEVIDWIDTH * dup; INT32 baseheight = BASEVIDHEIGHT * dup; SINT8 player = -1; UINT8 i; if (options & V_SPLITSCREEN) { if (r_splitscreen > 0) { screenheight /= 2; baseheight /= 2; } if (r_splitscreen > 1) { screenwidth /= 2; basewidth /= 2; } } for (i = 0; i <= r_splitscreen; i++) { if (stplyr == &players[displayplayers[i]]) { player = i; break; } } if (vid.width != (BASEVIDWIDTH * dup)) { if (options & V_SNAPTORIGHT) *x += (screenwidth - basewidth); else if (!(options & V_SNAPTOLEFT)) *x += (screenwidth - basewidth) / 2; } if (vid.height != (BASEVIDHEIGHT * dup)) { if (options & V_SNAPTOBOTTOM) *y += (screenheight - baseheight); else if (!(options & V_SNAPTOTOP)) *y += (screenheight - baseheight) / 2; } if (options & V_SPLITSCREEN) { if (r_splitscreen == 1) { if (player == 1) *y += screenheight; } else if (r_splitscreen > 1) { if (player == 1 || player == 3) *x += screenwidth; if (player == 2 || player == 3) *y += screenheight; } } if (options & V_SLIDEIN) { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; if (bossinfo.boss == true) { if (leveltime <= 3) timer = 0; else timer = leveltime-3; } if (timer < length) { boolean slidefromright = false; const INT32 offsetAmount = (screenwidth * FRACUNIT/2) / length; fixed_t offset = (screenwidth * FRACUNIT/2) - (timer * offsetAmount); offset += FixedMul(offsetAmount, renderdeltatics); offset /= FRACUNIT; if (r_splitscreen > 1) { if (stplyrnum == 1 || stplyrnum == 3) slidefromright = true; } if (options & V_SNAPTORIGHT) slidefromright = true; else if (options & V_SNAPTOLEFT) slidefromright = false; if (slidefromright == true) { offset = -offset; } *x -= offset; } } } static cliprect_t cliprect; const cliprect_t *V_GetClipRect(void) { if (cliprect.enabled == false) { return NULL; } return &cliprect; } static inline void V_GetClipBounds(INT32 *left, INT32 *right, INT32 *top, INT32 *bottom) { if (cliprect.enabled) { *left = cliprect.left; *right = cliprect.right; *top = cliprect.top; *bottom = cliprect.bottom; } else { *left = 0; *right = vid.width; *top = 0; *bottom = vid.height; } } void V_SetClipRect(fixed_t x, fixed_t y, fixed_t w, fixed_t h, INT32 flags) { // Adjust position. if (!(flags & V_NOSCALESTART)) { fixed_t dup = vid.dup; if (flags & V_SCALEPATCHMASK) { switch ((flags & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT) { case 1: // V_NOSCALEPATCH dup = 1; break; case 2: // V_SMALLSCALEPATCH dup = vid.smalldup; break; case 3: // V_MEDSCALEPATCH dup = vid.meddup; break; default: break; } } // fudge w/h to avoid precision loss (for carefully clipped drawfills) w += x % (FRACUNIT/dup); h += y % (FRACUNIT/dup); x = FixedMul(x, dup); y = FixedMul(y, dup); w = FixedMul(w, dup); h = FixedMul(h, dup); if (!(flags & V_SCALEPATCHMASK)) { V_AdjustXYWithSnap(&x, &y, flags, dup); } } if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x + w > vid.width) w = vid.width - x; if (y + h > vid.height) h = vid.height - y; x = min(x, vid.width); y = min(y, vid.height); w = max(w, 0); h = max(h, 0); cliprect.left = x; cliprect.top = y; cliprect.right = x + w; cliprect.bottom = y + h; cliprect.flags = flags; cliprect.enabled = true; /* V_DrawFill(cliprect.l, cliprect.t, cliprect.r - cliprect.l, cliprect.b - cliprect.t, V_NOSCALESTART); CONS_Printf("[(%d, %d), (%d, %d)]\n", cliprect.l, cliprect.t, cliprect.r, cliprect.b); */ } // sets up a cliprect for a splitscreen player (and actually accounts for non-green res) void V_SetClipRectForPlayer(UINT8 pnum) { INT32 w = vid.width, h = vid.height; if (r_splitscreen == 0) V_ClearClipRect(); // no need to else if (r_splitscreen == 1) V_SetClipRect(0, pnum ? h/2 : 0, w, h/2, V_NOSCALESTART); else V_SetClipRect(pnum & 1 ? w/2 : 0, pnum & 2 ? h/2 : 0, w/2, h/2, V_NOSCALESTART); } void V_ClearClipRect(void) { cliprect.enabled = false; } static UINT8 hudplusalpha[11] = { 10, 8, 6, 4, 2, 0, 0, 0, 0, 0, 0}; static UINT8 hudminusalpha[11] = { 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5}; UINT8 V_GetHudTransHalf(void) { return hudminusalpha[st_translucency]; } UINT8 V_GetHudTrans(void) { return 10 - st_translucency; } UINT8 V_GetHudTransDouble(void) { return hudplusalpha[st_translucency]; } static const UINT8 *v_colormap = NULL; static const UINT8 *v_translevel = NULL; #define STANDARDDRAW 1 #define MAPPEDDRAW 2 #define TRANSLUCENTDRAW 3 #define TRANSMAPPEDDRAW 4 /* static int sortcoords(const void *a, const void *b) { return *(const fixed_t *)a - *(const fixed_t *)b; } */ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 scrn, patch_t *patch, const UINT8 *colormap) { if (rendermode == render_none) return; INT32 dup; switch (scrn & V_SCALEPATCHMASK) { case V_NOSCALEPATCH: dup = 1; break; case V_SMALLSCALEPATCH: dup = vid.smalldup; break; case V_MEDSCALEPATCH: dup = vid.meddup; break; default: dup = vid.dup; break; } if (scrn & V_NOSCALESTART) { x >>= FRACBITS; y >>= FRACBITS; } else { x = FixedMul(x,dup<ox * (dup-1); y += transform->oy * (dup-1); x >>= FRACBITS; y >>= FRACBITS; if (!(scrn & V_SCALEPATCHMASK)) // Center it if necessary V_AdjustXYWithSnap(&x, &y, scrn, dup); } #ifdef HWRENDER if (rendermode == render_opengl) { HWR_DrawAffinePatch(patch, x, y, transform, scrn, colormap); return; } #endif Patch_GenerateFlat(patch, 0); const UINT16 *src = patch->flats[0]; if (src == NULL) return; const fixed_t a = transform->a / dup; const fixed_t b = transform->b / dup; const fixed_t c = transform->c / dup; const fixed_t d = transform->d / dup; const fixed_t cx = transform->ox; const fixed_t cy = transform->oy; const INT32 scrwidth = vid.width; const INT32 pw = patch->width, ph = patch->height; /* fixed_t determinant = FixedMul(a, d) - FixedMul(b, c); if (determinant == 0) return; fixed_t ba = FixedDiv(d, determinant); fixed_t bb = FixedDiv(-b, determinant); fixed_t bc = FixedDiv(-c, determinant); fixed_t bd = FixedDiv(a, determinant); fixed_t x1 = FixedMul(ba, -cx) + FixedMul(bb, -cy) + cx; fixed_t y1 = FixedMul(bc, -cx) + FixedMul(bd, -cy) + cy; fixed_t x2 = FixedMul(ba, pw*FRACUNIT - cx) + FixedMul(bb, -cy) + cx; fixed_t y2 = FixedMul(bc, pw*FRACUNIT - cx) + FixedMul(bd, -cy) + cy; fixed_t x3 = FixedMul(ba, -cx) + FixedMul(bb, ph*FRACUNIT - cy) + cx; fixed_t y3 = FixedMul(bc, -cx) + FixedMul(bd, ph*FRACUNIT - cy) + cy; fixed_t x4 = FixedMul(ba, pw*FRACUNIT - cx) + FixedMul(bb, ph*FRACUNIT - cy) + cx; fixed_t y4 = FixedMul(bc, pw*FRACUNIT - cx) + FixedMul(bd, ph*FRACUNIT - cy) + cy; */ UINT8 * const destbase = vid.screens[0] + y * vid.width + x; INT32 dx = 0, dy = 0; for (dy = 0; dy < ph; dy++) { // yoinked from NovaSquirrel's mode 7 preview // ...which is in turn yoinked from Mesen's S-PPU code // i can't do matrix math to save my life :face_holding_back_tears: // (m7xofs and m7yofs are already factored in by destbase) fixed_t ux = FixedMul(a, -cx) + FixedMul(b, -cy) + b*dy + cx; fixed_t uy = FixedMul(c, -cx) + FixedMul(d, -cy) + d*dy + cy; UINT8 *dest = destbase + dy * scrwidth; for (dx = 0; dx < pw; dx++, dest++) { const INT32 srcx = ux >> FRACBITS; const INT32 srcy = uy >> FRACBITS; ux += a; uy += c; if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) continue; const UINT16 pixel = src[srcy * pw + srcx]; if (pixel < 0xff00) continue; if (colormap != NULL) *dest = colormap[pixel & 0xff]; else *dest = (UINT8)(pixel & 0xff); } } /* fixed_t xsort[4] = { x1, x2, x3, x4 }; fixed_t ysort[4] = { y1, y2, y3, y4 }; qs22j(xsort, 4, sizeof(*xsort), sortcoords); qs22j(ysort, 4, sizeof(*ysort), sortcoords); fixed_t dx1, dx2; dx1 = FixedDiv(x1 - x2, y2 - y1); dx2 = FixedDiv(x1 - x3, y3 - y1); fixed_t px1 = x*FRACUNIT + x1; fixed_t px2 = x*FRACUNIT + x2; fixed_t py = y*FRACUNIT + y1; for (UINT32 i = 0; i < 10; i++) { for (fixed_t A = px1; A < px2; A += FRACUNIT) V_DrawFixedFill(A, py + i*FRACUNIT, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); px1 += dx1; px2 += dx2; } V_DrawFixedFill(x*FRACUNIT + x1, y*FRACUNIT + y1, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); V_DrawFixedFill(x*FRACUNIT + x2, y*FRACUNIT + y2, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); V_DrawFixedFill(x*FRACUNIT + x3, y*FRACUNIT + y3, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); V_DrawFixedFill(x*FRACUNIT + x4, y*FRACUNIT + y4, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); */ }; // draws a patch rotated counter-clockwise by angle degrees // note that scaling is applied BEFORE rotation! void V_DrawRotatedPatch(fixed_t x, fixed_t y, angle_t angle, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap) { fixed_t sa = FSIN(angle), ca = FCOS(angle); affine_t t = { .a = FixedDiv(ca, pscale), .b = FixedDiv(-sa, pscale), .c = FixedDiv(sa, vscale), .d = FixedDiv(ca, vscale), .ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT, .oy = (patch->pivot.y + patch->topoffset) * FRACUNIT, }; V_DrawAffinePatch(x, y, &t, scrn, patch, colormap); } // Draws a patch scaled to arbitrary size. void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap) { UINT8 patchdrawtype; UINT32 alphalevel, blendmode; fixed_t col, ofs, colfrac, rowfrac, fdup, vdup; INT32 dup; const column_t *column; UINT8 *desttop, *dest, *deststart, *destend; const UINT8 *source, *deststop; fixed_t pwidth; // patch width fixed_t offx = 0; // x offset const INT32 vidwidth = vid.width; const cliprect_t *clip = V_GetClipRect(); if (rendermode == render_none) return; #ifdef HWRENDER //if (rendermode != render_soft && !con_startup) // Why? if (rendermode == render_opengl) { HWR_DrawStretchyFixedPatch(patch, x, y, pscale, vscale, scrn, colormap); return; } #endif patchdrawtype = STANDARDDRAW; if ((blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT))) blendmode++; // realign to constants if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT))) { if (alphalevel == 13) // V_HUDTRANSHALF alphalevel = hudminusalpha[st_translucency]; else if (alphalevel == 14) // V_HUDTRANS alphalevel = 10 - st_translucency; else if (alphalevel == 15) // V_HUDTRANSDOUBLE alphalevel = hudplusalpha[st_translucency]; if (alphalevel >= 10) // Still inelegible to render? return; } if ((v_translevel = R_GetBlendTable(blendmode, alphalevel))) patchdrawtype = TRANSLUCENTDRAW; v_colormap = NULL; if (colormap) { v_colormap = colormap; patchdrawtype = (v_translevel) ? TRANSMAPPEDDRAW : MAPPEDDRAW; } dup = vid.dup; if (scrn & V_SCALEPATCHMASK) switch ((scrn & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT) { case 1: // V_NOSCALEPATCH dup = 1; break; case 2: // V_SMALLSCALEPATCH dup = vid.smalldup; break; case 3: // V_MEDSCALEPATCH dup = vid.meddup; break; default: break; } // only use one dup, to avoid stretching (har har) fdup = vdup = FixedMul(dup<width - patch->leftoffset)<leftoffset<height - patch->topoffset)<topoffset<>= FRACBITS; y >>= FRACBITS; desttop += (y*vid.width) + x; } else { x = FixedMul(x,dup<>= FRACBITS; y >>= FRACBITS; // Center it if necessary if (!(scrn & V_SCALEPATCHMASK)) { V_AdjustXYWithSnap(&x, &y, scrn, dup); } desttop += (y*vidwidth) + x; } if (pscale != FRACUNIT) // scale width properly { pwidth = patch->width<>= FRACBITS; } else pwidth = patch->width * dup; deststart = desttop; destend = desttop + pwidth; for (col = 0; (col>>FRACBITS) < patch->width; col += colfrac, ++offx, desttop++) { INT32 topdelta, prevdelta = -1; if (scrn & V_FLIP) // offx is measured from right edge instead of left { if (x+pwidth-offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION) break; if (x+pwidth-offx >= (clip ? clip->right : vidwidth)) // don't draw off the right of the screen (WRAP PREVENTION) continue; } else { if (x+offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION) continue; if (x+offx >= (clip ? clip->right : vidwidth)) // don't draw off the right of the screen (WRAP PREVENTION) break; } column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS])); switch (patchdrawtype) { case STANDARDDRAW: while (column->topdelta != 0xff) { fixed_t offy = 0; topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; source = (const UINT8 *)(column) + 3; dest = desttop; if (scrn & V_FLIP) dest = deststart + (destend - dest); topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup)); dest += topdelta * vidwidth; if (scrn & V_VFLIP) { for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta - offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = source[ofs>>FRACBITS]; dest += vidwidth; } } else { for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta + offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = source[ofs>>FRACBITS]; dest += vidwidth; } } column = (const column_t *)((const UINT8 *)column + column->length + 4); } break; case MAPPEDDRAW: while (column->topdelta != 0xff) { fixed_t offy = 0; topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; source = (const UINT8 *)(column) + 3; dest = desttop; if (scrn & V_FLIP) dest = deststart + (destend - dest); topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup)); dest += topdelta * vidwidth; if (scrn & V_VFLIP) { for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta - offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_colormap + source[ofs>>FRACBITS]); dest += vidwidth; } } else { for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta + offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_colormap + source[ofs>>FRACBITS]); dest += vidwidth; } } column = (const column_t *)((const UINT8 *)column + column->length + 4); } break; case TRANSMAPPEDDRAW: while (column->topdelta != 0xff) { fixed_t offy = 0; topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; source = (const UINT8 *)(column) + 3; dest = desttop; if (scrn & V_FLIP) dest = deststart + (destend - dest); topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup)); dest += topdelta * vidwidth; if (scrn & V_VFLIP) { for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta - offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff)); dest += vidwidth; } } else { for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta + offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff)); dest += vidwidth; } } column = (const column_t *)((const UINT8 *)column + column->length + 4); } break; case TRANSLUCENTDRAW: while (column->topdelta != 0xff) { fixed_t offy = 0; topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; source = (const UINT8 *)(column) + 3; dest = desttop; if (scrn & V_FLIP) dest = deststart + (destend - dest); topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup)); dest += topdelta * vidwidth; if (scrn & V_VFLIP) { for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta - offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff)); dest += vidwidth; } } else { for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy) { if (clip != NULL) { const INT32 cy = y + topdelta + offy; if (cy < clip->top) // don't draw off the top of the clip rect { dest += vidwidth; continue; } if (cy >= clip->bottom) // don't draw off the bottom of the clip rect { dest += vidwidth; continue; } } if (dest >= vid.screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = *(v_translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff)); dest += vidwidth; } } column = (const column_t *)((const UINT8 *)column + column->length + 4); } break; } } } // Draws a patch cropped and scaled to arbitrary size. void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h) { cliprect_t oldClip = cliprect; V_SetClipRect(x, y, w, h, scrn); x -= sx; y -= sy; V_DrawStretchyFixedPatch(x, y, pscale, pscale, scrn, patch, NULL); cliprect = oldClip; } // // V_DrawContinueIcon // Draw a mini player! If we can, that is. Otherwise we draw a star. // void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT16 skincolor) { (void)skinnum; (void)skincolor; V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_PATCH)); } // // V_DrawBlock // Draw a linear block of pixels into the view buffer. // void V_DrawBlock(INT32 x, INT32 y, INT32 scrn, INT32 width, INT32 height, const UINT8 *src) { UINT8 *dest; const UINT8 *deststop; #ifdef RANGECHECK if (x < 0 || x + width > vid.width || y < 0 || y + height > vid.height || (unsigned)scrn > 4) I_Error("Bad V_DrawBlock"); #endif dest = vid.screens[scrn] + y*vid.width + x; deststop = vid.screens[scrn] + vid.rowbytes * vid.height; while (height--) { memcpy(dest, src, width); src += width; dest += vid.width; if (dest > deststop) return; } } // // Fills a box of pixels with a single color, NOTE: scaled to screen size // void V_DrawFixedFill(fixed_t x, fixed_t y, INT32 w, INT32 h, INT32 c) { UINT8 *dest; const UINT8 *deststop; UINT32 alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT); UINT32 blendmode = ((c & V_BLENDMASK) >> V_BLENDSHIFT); //INT32 u; if (rendermode == render_none) return; v_translevel = NULL; if (alphalevel || blendmode) { if (alphalevel == 13) // V_HUDTRANSHALF alphalevel = hudminusalpha[st_translucency]; else if (alphalevel == 14) // V_HUDTRANS alphalevel = 10 - st_translucency; else if (alphalevel == 15) // V_HUDTRANSDOUBLE alphalevel = hudplusalpha[st_translucency]; if (alphalevel >= 10) return; // invis if (alphalevel || blendmode) v_translevel = R_GetBlendTable(blendmode+1, alphalevel); } if (!(c & V_NOSCALESTART)) { INT32 dup = vid.dup; if (x == 0 && y == 0 && w == BASEVIDWIDTH*FRACUNIT && h == BASEVIDHEIGHT*FRACUNIT) { // Clear the entire screen, from dest to deststop. Yes, this really works. #ifdef HWRENDER if (rendermode == render_opengl) goto fillscreen; #endif memset(vid.screens[0], (c&255), vid.width * vid.height); return; } x = FixedMul(x, dup); y = FixedMul(y, dup); w = FixedMul(w, dup); h = FixedMul(h, dup); // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dup); } else { x >>= FRACBITS; y >>= FRACBITS; w >>= FRACBITS; h >>= FRACBITS; } INT32 left, right, top, bottom; V_GetClipBounds(&left, &right, &top, &bottom); if (x >= right || y >= bottom) return; // off the screen if (x < left) { w += x - left; x = left; } if (y < top) { h += y - top; y = top; } if (w <= 0 || h <= 0) return; // zero width/height wouldn't draw anything if (x + w > right) w = right - x; if (y + h > bottom) h = bottom - y; #ifdef HWRENDER //if (rendermode != render_soft && !con_startup) // Not this again if (rendermode == render_opengl) { fillscreen: HWR_DrawFill(x, y, w, h, c); return; } #endif dest = vid.screens[0] + y*vid.width + x; deststop = vid.screens[0] + vid.rowbytes * vid.height; c &= 255; // borrowing this from jimitia's new hud drawing functions rq if (alphalevel) { v_translevel += c<<8; for (;(--h >= 0) && dest < deststop; dest += vid.width) { for (x = 0; x < w; x++) dest[x] = v_translevel[dest[x]]; } } else { for (;(--h >= 0) && dest < deststop; dest += vid.width) memset(dest, c, w); } } #ifdef HWRENDER // This is now a function since it's otherwise repeated 2 times and honestly looks retarded: static UINT32 V_GetHWConsBackColor(void) { RGBA_t output; switch (cons_backcolor.value) { case 0: output.s.red = 0xff; output.s.green = 0xff; output.s.blue = 0xff; break; // White case 1: output.s.red = 0x80; output.s.green = 0x80; output.s.blue = 0x80; break; // Black case 2: output.s.red = 0xde; output.s.green = 0xb8; output.s.blue = 0x87; break; // Sepia case 3: output.s.red = 0x40; output.s.green = 0x20; output.s.blue = 0x10; break; // Brown case 4: output.s.red = 0xfa; output.s.green = 0x80; output.s.blue = 0x72; break; // Pink case 5: output.s.red = 0xff; output.s.green = 0x69; output.s.blue = 0xb4; break; // Raspberry case 6: output.s.red = 0xff; output.s.green = 0x00; output.s.blue = 0x00; break; // Red case 7: output.s.red = 0xff; output.s.green = 0xd6; output.s.blue = 0x83; break; // Creamsicle case 8: output.s.red = 0xff; output.s.green = 0x80; output.s.blue = 0x00; break; // Orange case 9: output.s.red = 0xda; output.s.green = 0xa5; output.s.blue = 0x20; break; // Gold case 10: output.s.red = 0x80; output.s.green = 0x80; output.s.blue = 0x00; break; // Yellow case 11: output.s.red = 0x00; output.s.green = 0xff; output.s.blue = 0x00; break; // Emerald case 12: output.s.red = 0x00; output.s.green = 0x80; output.s.blue = 0x00; break; // Green case 13: output.s.red = 0x40; output.s.green = 0x80; output.s.blue = 0xff; break; // Cyan case 14: output.s.red = 0x46; output.s.green = 0x82; output.s.blue = 0xb4; break; // Steel case 15: output.s.red = 0x1e; output.s.green = 0x90; output.s.blue = 0xff; break; // Periwinkle case 16: output.s.red = 0x00; output.s.green = 0x00; output.s.blue = 0xff; break; // Blue case 17: output.s.red = 0xff; output.s.green = 0x00; output.s.blue = 0xff; break; // Purple case 18: output.s.red = 0xee; output.s.green = 0x82; output.s.blue = 0xee; break; // Lavender // Default green default: output.s.red = 0x00; output.s.green = 0x80; output.s.blue = 0x00; break; } V_CubeApply(&output); return (output.s.red << 24) | (output.s.green << 16) | (output.s.blue << 8); } #endif // THANK YOU MPC!!! // and thanks toaster for cleaning it up. void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { UINT8 *dest; const UINT8 *deststop; INT32 u; UINT8 *fadetable; UINT32 alphalevel = 0; if (rendermode == render_none) return; #ifdef HWRENDER if (rendermode == render_opengl) { UINT32 hwcolor = V_GetHWConsBackColor(); HWR_DrawConsoleFill(x, y, w, h, c, hwcolor); // we still use the regular color stuff but only for flags. actual draw color is "hwcolor" for this. return; } #endif if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT))) { if (alphalevel == 13) // V_HUDTRANSHALF alphalevel = hudminusalpha[st_translucency]; else if (alphalevel == 14) // V_HUDTRANS alphalevel = 10 - st_translucency; else if (alphalevel == 15) // V_HUDTRANSDOUBLE alphalevel = hudplusalpha[st_translucency]; if (alphalevel >= 10) // Still inelegible to render? return; } if (!(c & V_NOSCALESTART)) { INT32 dup = vid.dup; x *= dup; y *= dup; w *= dup; h *= dup; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dup); } if (x >= vid.width || y >= vid.height) return; // off the screen if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (w <= 0 || h <= 0) return; // zero width/height wouldn't draw anything if (x + w > vid.width) w = vid.width-x; if (y + h > vid.height) h = vid.height-y; dest = vid.screens[0] + y*vid.width + x; deststop = vid.screens[0] + vid.rowbytes * vid.height; c &= 255; // Jimita (12-04-2018) if (alphalevel) { fadetable = R_GetTranslucencyTable(alphalevel) + (c*256); for (;(--h >= 0) && dest < deststop; dest += vid.width) { u = 0; while (u < w) { dest[u] = fadetable[consolebgmap[dest[u]]]; u++; } } } else { for (;(--h >= 0) && dest < deststop; dest += vid.width) { u = 0; while (u < w) { dest[u] = consolebgmap[dest[u]]; u++; } } } } // // Fills a triangle of pixels with a single color, NOTE: scaled to screen size // // ... // .. <-- this shape only for now, i'm afraid // . // void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) { UINT8 *dest; const UINT8 *deststop; INT32 w, h, wait = 0; if (rendermode == render_none) return; #ifdef HWRENDER if (rendermode == render_opengl) { HWR_DrawDiag(x, y, wh, c); return; } #endif if (!(c & V_NOSCALESTART)) { INT32 dup = vid.dup; x *= dup; y *= dup; wh *= dup; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dup); } if (x >= vid.width || y >= vid.height) return; // off the screen if (y < 0) { wh += y; y = 0; } w = h = wh; if (x < 0) { w += x; x = 0; } if (w <= 0 || h <= 0) return; // zero width/height wouldn't draw anything if (x + w > vid.width) { wait = w - (vid.width - x); w = vid.width - x; } if (y + w > vid.height) h = vid.height - y; if (h > w) h = w; dest = vid.screens[0] + y*vid.width + x; deststop = vid.screens[0] + vid.rowbytes * vid.height; c &= 255; for (;(--h >= 0) && dest < deststop; dest += vid.width) { memset(dest, c, w); if (wait) wait--; else w--; } } // // If color is 0x00 to 0xFF, draw transtable (strength range 0-9). // Else, use COLORMAP lump (strength range 0-31). // c is not color, it is for flags only. transparency flags will be ignored. // IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH! // I have kept the safety checks for strength out of this function; // I don't trust Lua users with it, so it doesn't matter. // void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength) { UINT8 *dest; const UINT8 *deststop; INT32 u; UINT8 *fadetable; if (rendermode == render_none) return; #ifdef HWRENDER if (rendermode == render_opengl) { // ughhhhh please can someone else do this? thanks ~toast 25/7/19 in 38 degrees centigrade w/o AC HWR_DrawFadeFill(x, y, w, h, c, color, strength); // toast two days later - left above comment in 'cause it's funny return; } #endif if (!(c & V_NOSCALESTART)) { INT32 dup = vid.dup; x *= dup; y *= dup; w *= dup; h *= dup; // Center it if necessary // adjustxy } if (x >= vid.width || y >= vid.height) return; // off the screen if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (w <= 0 || h <= 0) return; // zero width/height wouldn't draw anything if (x + w > vid.width) w = vid.width-x; if (y + h > vid.height) h = vid.height-y; dest = vid.screens[0] + y*vid.width + x; deststop = vid.screens[0] + vid.rowbytes * vid.height; c &= 255; fadetable = ((color & 0xFF00) // Color is not palette index? ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. : ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade. for (;(--h >= 0) && dest < deststop; dest += vid.width) { u = 0; while (u < w) { dest[u] = fadetable[dest[u]]; u++; } } } // // Fills a box of pixels using a flat texture as a pattern, scaled to screen size. // void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum) { INT32 u, v, dup; fixed_t dx, dy, xfrac, yfrac; const UINT8 *src, *deststop; UINT8 *flat, *dest; size_t size, lflatsize, flatshift; #ifdef HWRENDER if (rendermode == render_opengl) { HWR_DrawFlatFill(x, y, w, h, flatnum); return; } #endif size = W_LumpLength(flatnum); switch (size) { case 4194304: // 2048x2048 lump lflatsize = 2048; flatshift = 11; break; case 1048576: // 1024x1024 lump lflatsize = 1024; flatshift = 10; break; case 262144:// 512x512 lump lflatsize = 512; flatshift = 9; break; case 65536: // 256x256 lump lflatsize = 256; flatshift = 8; break; case 16384: // 128x128 lump lflatsize = 128; flatshift = 7; break; case 1024: // 32x32 lump lflatsize = 32; flatshift = 5; break; case 256: // 16x16 lump lflatsize = 16; flatshift = 4; break; case 64: // 8x8 lump lflatsize = 8; flatshift = 3; break; default: // 64x64 lump lflatsize = 64; flatshift = 6; break; } flat = W_CacheLumpNum(flatnum, PU_CACHE); dup = vid.dup; dest = vid.screens[0] + y*dup*vid.width + x*dup; deststop = vid.screens[0] + vid.rowbytes * vid.height; // from V_DrawScaledPatch if (vid.width != BASEVIDWIDTH * dup) { // dup adjustments pretend that screen width is BASEVIDWIDTH * dup, // so center this imaginary screen dest += (vid.width - (BASEVIDWIDTH * dup)) / 2; } if (vid.height != BASEVIDHEIGHT * dup) { // same thing here dest += (vid.height - (BASEVIDHEIGHT * dup)) * vid.width / 2; } w *= dup; h *= dup; dx = FixedDiv(FRACUNIT, dup<<(FRACBITS-2)); dy = FixedDiv(FRACUNIT, dup<<(FRACBITS-2)); yfrac = 0; for (v = 0; v < h; v++, dest += vid.width) { xfrac = 0; src = flat + (((yfrac>>FRACBITS) & (lflatsize - 1)) << flatshift); for (u = 0; u < w; u++) { if (&dest[u] > deststop) return; dest[u] = src[(xfrac>>FRACBITS)&(lflatsize-1)]; xfrac += dx; } yfrac += dy; } } // // V_DrawPatchFill // void V_DrawPatchFill(patch_t *pat) { INT32 x, y, pw = pat->width * vid.dup, ph = pat->height * vid.dup; for (x = 0; x < vid.width; x += pw) { for (y = 0; y < vid.height; y += ph) V_DrawScaledPatch(x, y, V_NOSCALESTART, pat); } } // Draws a patch and tries to always fill the screen with the patch void V_DrawAdaptiveScaledFullScreenPatch(patch_t *patch) { fixed_t x = 0, y = 0; fixed_t scale = ((vid.width * FRACUNIT) / patch->width); // fit the screen horizontally fixed_t scaled_height = FixedMul(patch->height << FRACBITS, scale); // however, if this means the patch doesent fill out the screen vertically then if (scaled_height < (vid.height << FRACBITS)) { scale = ((vid.height * FRACUNIT) / patch->height); // scale it to fit the screen vertically x = ((vid.width << FRACBITS) - FixedMul(patch->width << FRACBITS, scale)) / 2; } else y = (vid.height << FRACBITS) - scaled_height; V_DrawFixedPatch(x, y, scale, V_NOSCALEPATCH, patch, NULL); } // Draws a patch and scales it to fill out the screen vertically while remaining centered void V_DrawVerticallyScaledFullScreenPatch(patch_t *patch) { fixed_t scale = ((vid.height * FRACUNIT) / patch->height); fixed_t x = ((vid.width << FRACBITS) - FixedMul(patch->width << FRACBITS, scale)) / 2; // i fucking hate maths V_DrawFixedPatch(x, 0, scale, V_NOSCALEPATCH, patch, NULL); } // Draws a patch and scales it to fill out the screen horizontally // centers the patch when its too small to fit the screen vertically void V_DrawHorizontallyScaledFullScreenPatch(patch_t *patch) { fixed_t scale = ((vid.width * FRACUNIT) / patch->width); fixed_t scaled_height = FixedMul(patch->height << FRACBITS, scale); fixed_t y = (vid.height << FRACBITS) - scaled_height; // center it if it does not fit the screen vertically if (scaled_height < (vid.height << FRACBITS)) { y /= 2; } V_DrawFixedPatch(0, y, scale, V_NOSCALEPATCH, patch, NULL); } void V_DrawVhsEffect(boolean rewind) { fixed_t uby, dby; // upbary is the bar going from top to bottom for some reason static fixed_t upbary = 100*FRACUNIT, downbary = 150*FRACUNIT; UINT8 barsize, updistort, downdistort; UINT16 y; UINT32 x, pos; UINT8 *buf, *tmp; UINT8 *normalmapstart, *thismapstart; #ifdef HQ_VHS UINT8 *tmapstart; #endif SINT8 offs; barsize = vid.udup << 5; updistort = vid.udup << (rewind ? 5 : 3); downdistort = updistort >> 1; if (rewind) V_DrawVhsEffect(false); // experimentation upbary -= renderdeltatics * (fixed_t)(vid.udup * (rewind ? 3 : 1.8f)); downbary += renderdeltatics * (vid.udup * (rewind ? 2 : 1)); if (upbary < -barsize*FRACUNIT) upbary = vid.height << FRACBITS; if (upbary > vid.height << FRACBITS) upbary = -barsize*FRACUNIT; if (downbary > vid.height << FRACBITS) downbary = -barsize*FRACUNIT; if (downbary < -barsize*FRACUNIT) downbary = vid.height << FRACBITS; uby = upbary >> FRACBITS; dby = downbary >> FRACBITS; #ifdef HWRENDER if (rendermode == render_opengl) { HWR_RenderVhsEffect(uby, dby, updistort, downdistort, barsize); return; } #endif buf = vid.screens[0]; tmp = vid.screens[4]; normalmapstart = ((UINT8 *)transtables + (8<= uby && y < uby+barsize) { thismapstart -= (2<= dby && y < dby+barsize) { thismapstart -= (2<= vid.height-2 && offs > 0)) offs = 0; for (x = min(pos+(size_t)vid.width*2, (size_t)vid.width*vid.height); pos < x; pos++) { tmp[pos] = thismapstart[buf[pos+offs]]; #ifdef HQ_VHS tmp[pos] = tmapstart[buf[pos]<<8 | tmp[pos]]; #endif } } memcpy(buf, tmp, vid.width*vid.height); } // // Fade all the screen buffer, so that the menu is more readable, // especially now that we use the small hufont in the menus... // If color is 0x00 to 0xFF, draw transtable (strength range 0-9). // Else, use COLORMAP lump (strength range 0-31). // IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH! // I have kept the safety checks out of this function; // the v.fadeScreen Lua interface handles those. // void V_DrawFadeScreen(UINT16 color, UINT8 strength) { #ifdef HWRENDER if (rendermode == render_opengl) { HWR_FadeScreenMenuBack(color, strength); return; } #endif { const UINT8 *fadetable = (color > 0xFFF0) // Grab a specific colormap palette? ? R_GetTranslationColormap(color | 0xFFFF0000, strength, GTC_CACHE) : ((color & 0xFF00) // Color is not palette index? ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. : ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade. const UINT8 *deststop = vid.screens[0] + vid.rowbytes * vid.height; UINT8 *buf = vid.screens[0]; // heavily simplified -- we don't need to know x or y // position when we're doing a full screen fade for (; buf < deststop; ++buf) *buf = fadetable[*buf]; } } // // Fade the screen buffer, using a custom COLORMAP lump. // Split from V_DrawFadeScreen, because that function has // WAY too many options piled on top of it as is. :V // void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) { #ifdef HWRENDER if (rendermode != render_soft && rendermode != render_none) { //HWR_DrawCustomFadeScreen(color, strength); return; } #endif { lumpnum_t lumpnum = LUMPERROR; lighttable_t *clm = NULL; if (lump != NULL) lumpnum = W_GetNumForName(lump); else return; if (lumpnum != LUMPERROR) { clm = Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8); W_ReadLump(lumpnum, clm); if (clm != NULL) { const UINT8 *fadetable = ((UINT8 *)clm + strength*256); const UINT8 *deststop = vid.screens[0] + vid.rowbytes * vid.height; UINT8 *buf = vid.screens[0]; // heavily simplified -- we don't need to know x or y // position when we're doing a full screen fade for (; buf < deststop; ++buf) *buf = fadetable[*buf]; Z_Free(clm); clm = NULL; } } } } // Simple translucency with one color, over a set number of lines starting from the top. void V_DrawFadeConsBack(INT32 plines) { UINT8 *deststop, *buf; #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode == render_opengl) { UINT32 hwcolor = V_GetHWConsBackColor(); HWR_DrawConsoleBack(hwcolor, plines); return; } #endif // heavily simplified -- we don't need to know x or y position, // just the stop position deststop = vid.screens[0] + vid.rowbytes * min(plines, vid.height); for (buf = vid.screens[0]; buf < deststop; ++buf) *buf = consolebgmap[*buf]; } // // Invert the entire screen, for Encore fades // void V_EncoreInvertScreen(void) { #ifdef HWRENDER if (rendermode != render_soft && rendermode != render_none) { HWR_EncoreInvertScreen(); return; } #endif { const UINT8 *deststop = vid.screens[0] + vid.rowbytes * vid.height; UINT8 *buf = vid.screens[0]; for (; buf < deststop; ++buf) { *buf = NearestColor( 255 - pLocalPalette[*buf].s.red, 255 - pLocalPalette[*buf].s.green, 255 - pLocalPalette[*buf].s.blue ); } } } // Very similar to F_DrawFadeConsBack, except we draw from the middle(-ish) of the screen to the bottom. void V_DrawPromptBack(INT32 boxheight, INT32 color) { UINT8 *deststop, *buf; if (color >= 256 && color < 512) { if (boxheight < 0) boxheight = -boxheight; else // 4 lines of space plus gaps between and some leeway boxheight = ((boxheight * 4) + (boxheight/2)*5); V_DrawFill((BASEVIDWIDTH-(vid.scaledwidth))/2, BASEVIDHEIGHT-boxheight, (vid.scaledwidth),boxheight, (color-256)|V_SNAPTOBOTTOM); return; } boxheight *= vid.dup; if (color == INT32_MAX) color = cons_backcolor.value; #ifdef HWRENDER if (rendermode == render_opengl) { UINT32 hwcolor; RGBA_t output; switch (color) { case 0: output.s.red = 0xff; output.s.green = 0xff; output.s.blue = 0xff; break; // White case 1: output.s.red = 0x80; output.s.green = 0x80; output.s.blue = 0x80; break; // Black case 2: output.s.red = 0xde; output.s.green = 0xb8; output.s.blue = 0x87; break; // Sepia case 3: output.s.red = 0x40; output.s.green = 0x20; output.s.blue = 0x10; break; // Brown case 4: output.s.red = 0xfa; output.s.green = 0x80; output.s.blue = 0x72; break; // Pink case 5: output.s.red = 0xff; output.s.green = 0x69; output.s.blue = 0xb4; break; // Raspberry case 6: output.s.red = 0xff; output.s.green = 0x00; output.s.blue = 0x00; break; // Red case 7: output.s.red = 0xff; output.s.green = 0xd6; output.s.blue = 0x83; break; // Creamsicle case 8: output.s.red = 0xff; output.s.green = 0x80; output.s.blue = 0x00; break; // Orange case 9: output.s.red = 0xda; output.s.green = 0xa5; output.s.blue = 0x20; break; // Gold case 10: output.s.red = 0x80; output.s.green = 0x80; output.s.blue = 0x00; break; // Yellow case 11: output.s.red = 0x00; output.s.green = 0xff; output.s.blue = 0x00; break; // Emerald case 12: output.s.red = 0x00; output.s.green = 0x80; output.s.blue = 0x00; break; // Green case 13: output.s.red = 0x40; output.s.green = 0x80; output.s.blue = 0xff; break; // Cyan case 14: output.s.red = 0x46; output.s.green = 0x82; output.s.blue = 0xb4; break; // Steel case 15: output.s.red = 0x1e; output.s.green = 0x90; output.s.blue = 0xff; break; // Periwinkle case 16: output.s.red = 0x00; output.s.green = 0x00; output.s.blue = 0xff; break; // Blue case 17: output.s.red = 0xff; output.s.green = 0x00; output.s.blue = 0xff; break; // Purple case 18: output.s.red = 0xee; output.s.green = 0x82; output.s.blue = 0xee; break; // Lavender // Default green default: output.s.red = 0x00; output.s.green = 0x80; output.s.blue = 0x00; break; } V_CubeApply(&output); hwcolor = (output.s.red << 24) | (output.s.green << 16) | (output.s.blue << 8); HWR_DrawTutorialBack(hwcolor, boxheight); return; } #endif CON_SetupBackColormapEx(color, true); // heavily simplified -- we don't need to know x or y position, // just the start and stop positions buf = deststop = vid.screens[0] + vid.rowbytes * vid.height; if (boxheight < 0) buf += vid.rowbytes * boxheight; else // 4 lines of space plus gaps between and some leeway buf -= vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5); for (; buf < deststop; ++buf) *buf = promptbgmap[*buf]; } // Gets string colormap, used for 0x80 color codes // UINT8 *V_GetStringColormap(INT32 colorflags) { #if 0 // perfect switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT) { case 1: // 0x81, purple return purplemap; case 2: // 0x82, yellow return yellowmap; case 3: // 0x83, green return greenmap; case 4: // 0x84, blue return bluemap; case 5: // 0x85, red return redmap; case 6: // 0x86, gray return graymap; case 7: // 0x87, orange return orangemap; case 8: // 0x88, sky return skymap; case 9: // 0x89, lavender return lavendermap; case 10: // 0x8A, gold return goldmap; case 11: // 0x8B, aqua-green return aquamap; case 12: // 0x8C, magenta return magentamap; case 13: // 0x8D, pink return pinkmap; case 14: // 0x8E, brown return brownmap; case 15: // 0x8F, tan return tanmap; default: // reset return NULL; } #else // optimised colorflags = ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT); if (!colorflags || colorflags > 15) // INT32 is signed, but V_CHARCOLORMASK is a very restrictive mask. return NULL; return (purplemap+((colorflags-1)<<8)); #endif } INT32 V_DanceYOffset(INT32 counter) { const INT32 duration = 16; const INT32 step = (I_GetTime() + counter) % duration; return abs(step - (duration / 2)) - (duration / 4); } static boolean V_CharacterValid(font_t *font, int c) { return (c >= 0 && c < font->size && font->font[c] != NULL); } // Writes a single character (draw WHITE if bit 7 set) // void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed) { INT32 w, flags; const UINT8 *colormap = V_GetStringColormap(c); flags = c & ~(V_CHARCOLORMASK | V_PARAMMASK); c &= 0x7f; if (lowercaseallowed) c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; w = fontv[HU_FONT].font[c]->width; if (x + w > vid.width) return; if (colormap != NULL) V_DrawMappedPatch(x, y, flags, fontv[HU_FONT].font[c], colormap); else V_DrawScaledPatch(x, y, flags, fontv[HU_FONT].font[c]); } // Writes a single character for the chat (half scaled). (draw WHITE if bit 7 set) // 16/02/19: Scratch the scaling thing, chat doesn't work anymore under 2x res -Lat' // void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap) { INT32 w, flags; //const UINT8 *colormap = V_GetStringColormap(c); flags = c & ~(V_CHARCOLORMASK | V_PARAMMASK); c &= 0x7f; if (lowercaseallowed) c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; w = fontv[HU_FONT].font[c]->width / 2; if (x + w > vid.width) return; V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/2, flags, fontv[HU_FONT].font[c], colormap); } // V_TitleCardStringWidth // Get the string's width using the titlecard font. INT32 V_TitleCardStringWidth(const char *str) { INT32 xoffs = 0; const char *ch = str; char c; patch_t *pp; for (;;ch++) { if (!*ch) break; if (*ch == '\n') { xoffs = 0; continue; } c = *ch; c = toupper(c); c -= LT_FONTSTART; // check if character exists, if not, it's a space. if (c < 0 || c >= LT_FONTSIZE || !fontv[GTOL_FONT].font[(INT32)c]) { xoffs += 10; continue; } pp = fontv[GTFN_FONT].font[(INT32)c]; xoffs += pp->width-5; } return xoffs; } // V_DrawTitleCardScreen. // see v_video.h's prototype for more information. // void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold) { INT32 xoffs = 0; INT32 yoffs = 0; INT32 i = 0; // per-letter variables fixed_t scalex; fixed_t offs; INT32 let_time; INT32 flipflag; angle_t fakeang; const char *ch = str; char c; patch_t *pp; patch_t *ol; x -= 2; // Account for patch width... if (flags & V_SNAPTORIGHT) x -= V_TitleCardStringWidth(str); for (;;ch++, i++) { scalex = FRACUNIT; offs = 0; let_time = timer - i; flipflag = 0; if (!*ch) break; if (*ch == '\n') { xoffs = x; yoffs += 32; continue; } c = *ch; c = toupper(c); c -= LT_FONTSTART; // check if character exists, if not, it's a space. if (c < 0 || c >= LT_FONTSIZE || !fontv[GTFN_FONT].font[(INT32)c]) { xoffs += 10; continue; } ol = fontv[GTOL_FONT].font[(INT32)c]; pp = fontv[GTFN_FONT].font[(INT32)c]; if (bossmode) { if (let_time <= 0) return; if (threshold > 0) { if (threshold > 3) return; fakeang = (threshold*ANGLE_45)/2; scalex = FINECOSINE(fakeang>>ANGLETOFINESHIFT); } offs = ((FRACUNIT-scalex)*pp->width)/2; } else if (timer) { // make letters appear if (!threshold) ; else if (let_time < threshold) { if (let_time <= 0) return; // No reason to continue drawing, none of the next letters will be drawn either. // otherwise; scalex must start at 0 // let's have each letter do 4 spins (360*4 + 90 = 1530 "degrees") fakeang = min(360 + 90, let_time*41) * ANG1; scalex = FINESINE(fakeang>>ANGLETOFINESHIFT); } else if (!bossmode && let_time > threshold) { // Make letters disappear... let_time -= threshold; fakeang = max(0, (360+90) - let_time*41)*ANG1; scalex = FINESINE(fakeang>>ANGLETOFINESHIFT); } // Because of how our patches are offset, we need to counter the displacement caused by changing the scale with an offset of our own. offs = ((FRACUNIT-scalex)*pp->width)/2; } // And now, we just need to draw the stuff. flipflag = (scalex < 0) ? V_FLIP : 0; if (scalex && ol && pp) { //CONS_Printf("%d\n", (INT32)c); V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, ol, NULL); V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, pp, NULL); } xoffs += pp->width -5; } } // Precompile a wordwrapped string to any given width. // This is a muuuch better method than V_WORDWRAP. char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) { int c; size_t chw, i, lastusablespace = 0; size_t slen; char *newstring = Z_StrDup(string); INT32 spacewidth = 4, charwidth = 0; slen = strlen(string); if (w == 0) w = BASEVIDWIDTH; w -= x; x = 0; switch (option & V_SPACINGMASK) { case V_MONOSPACE: spacewidth = 8; /* FALLTHRU */ case V_OLDSPACING: charwidth = 8; break; case V_6WIDTHSPACE: spacewidth = 6; default: break; } for (i = 0; i < slen; ++i) { c = newstring[i]; if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 continue; if (c == '\n') { x = 0; lastusablespace = 0; continue; } if (!(option & V_ALLOWLOWERCASE)) c = toupper(c); c -= HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) { chw = spacewidth; lastusablespace = i; } else chw = (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); x += chw; if (lastusablespace != 0 && x > w) { newstring[lastusablespace] = '\n'; i = lastusablespace; lastusablespace = 0; x = 0; } } return newstring; } static inline fixed_t FixedCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dup, fixed_t * cwp) { (void)scale; (void)hchw; (void)dup; (*cwp) = chw; return 0; } static inline fixed_t VariableCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dup, fixed_t * cwp) { (void)chw; (void)hchw; (void)dup; (*cwp) = FixedMul ((*cwp) << FRACBITS, scale); return 0; } static inline fixed_t CenteredCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dup, fixed_t * cwp) { INT32 cxoff; /* For example, center a 4 wide patch to 8 width: 4/2 = 2 8/2 = 4 4 - 2 = 2 (our offset) 2 + 4 = 6 = 8 - 2 (equal space on either side) */ cxoff = hchw -((*cwp) >> 1 ); (*cwp) = chw; return FixedMul (( cxoff * dup )<< FRACBITS, scale); } static inline fixed_t BunchedCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dup, fixed_t * cwp) { (void)chw; (void)hchw; (void)dup; (*cwp) = FixedMul (max (1, (*cwp) - 1) << FRACBITS, scale); return 0; } typedef struct { fixed_t chw; fixed_t spacew; fixed_t lfh; fixed_t (*dim_fn)(fixed_t,fixed_t,INT32,INT32,fixed_t *); } fontspec_t; static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) { /* Hardcoded until a better system can be implemented for determining how fonts space. */ // All other properties are guaranteed to be set result->chw = 0; const INT32 spacing = ( flags & V_SPACINGMASK ); switch (fontno) { default: case HU_FONT: result->spacew = 4; switch (spacing) { case V_MONOSPACE: result->spacew = 8; /* FALLTHRU */ case V_OLDSPACING: result->chw = 8; break; case V_6WIDTHSPACE: result->spacew = 6; } break; case TINY_FONT: result->spacew = 2; switch (spacing) { case V_MONOSPACE: result->spacew = 5; /* FALLTHRU */ case V_OLDSPACING: result->chw = 5; break; // Out of video flags, so we're reusing this for alternate charwidth instead /*case V_6WIDTHSPACE: spacewidth = 3;*/ } break; case KART_FONT: result->spacew = 12; switch (spacing) { case V_MONOSPACE: result->spacew = 12; /* FALLTHRU */ case V_OLDSPACING: result->chw = 12; break; case V_6WIDTHSPACE: result->spacew = 6; } break; case LT_FONT: result->spacew = 12; break; case CRED_FONT: result->spacew = 16; break; } switch (fontno) { default: case HU_FONT: case TINY_FONT: case KART_FONT: result->lfh = 12; break; case LT_FONT: case CRED_FONT: result->lfh = 12; break; } switch (fontno) { case TINY_FONT: { if (result->chw) result->dim_fn = FixedCharacterDim; else { /* Reuse this flag for the alternate bunched-up spacing. */ if (( flags & V_6WIDTHSPACE )) result->dim_fn = BunchedCharacterDim; else result->dim_fn = VariableCharacterDim; } break; } default: { if (result->chw) result->dim_fn = CenteredCharacterDim; else result->dim_fn = VariableCharacterDim; break; } } } void V_DrawStringScaledEx( fixed_t x, fixed_t y, fixed_t scale, fixed_t vscale, fixed_t spacescale, fixed_t lfscale, INT32 flags, const UINT8 *colormap, int fontno, const char *s) { INT32 hchw;/* half-width for centering */ INT32 dup; fixed_t right; fixed_t bot; font_t *font; boolean uppercase; boolean notcolored; boolean vflip; boolean dance; boolean nodanceoverride; INT32 dancecounter; fixed_t cx, cy; fixed_t cxoff, cyoff; fixed_t cw; fixed_t left; int c; uppercase = !( flags & V_ALLOWLOWERCASE ); flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ dance = (flags & V_STRINGDANCE) != 0; nodanceoverride = !dance; dancecounter = 0; vflip = (flags & V_VFLIP) != 0; /* Some of these flags get overloaded in this function so don't pass them on. */ flags &= ~(V_PARAMMASK); if (vflip) flags |= V_VFLIP; if (colormap == NULL) { colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); } notcolored = !colormap; font = &fontv[fontno]; fontspec_t fontspec; V_GetFontSpecification(fontno, flags, &fontspec); hchw = fontspec.chw >> 1; fontspec.chw <<= FRACBITS; fontspec.spacew <<= FRACBITS; fontspec.lfh <<= FRACBITS; #define Mul( id, scale ) ( id = FixedMul (scale, id) ) Mul (fontspec.chw, scale); Mul (fontspec.spacew, scale); Mul (fontspec.lfh, scale); Mul (fontspec.spacew, spacescale); Mul (fontspec.lfh, lfscale); #undef Mul if (( flags & V_NOSCALESTART )) { dup = vid.dup; hchw *= dup; fontspec.chw *= dup; fontspec.spacew *= dup; fontspec.lfh *= dup; right = vid.width; } else { dup = 1; right = ( vid.scaledwidth ); if (!( flags & V_SNAPTOLEFT )) { left = ( right - BASEVIDWIDTH )/ 2;/* left edge of drawable area */ right -= left; } } right <<= FRACBITS; bot = vid.height << FRACBITS; cx = x; cy = y; cyoff = 0; for (; ( c = *s ); ++s, ++dancecounter) { switch (c) { case '\n': cy += fontspec.lfh; if (cy >= bot) return; cx = x; break; default: if (( c & 0xF0 ) == 0x80) { if (notcolored) { colormap = V_GetStringColormap( ( ( c & 0x7f )<< V_CHARCOLORSHIFT )& V_CHARCOLORMASK); } if (nodanceoverride) { dance = false; cyoff = 0; } } else if (c == V_STRINGDANCE) { dance = true; } else if (cx < right) { if (uppercase) { c = toupper(c); } else if (V_CharacterValid(font, c - font->start) == false) { // Try the other case if it doesn't exist if (c >= 'A' && c <= 'Z') { c = tolower(c); } else if (c >= 'a' && c <= 'z') { c = toupper(c); } } if (dance) { cyoff = V_DanceYOffset(dancecounter) * FRACUNIT; } c -= font->start; if (V_CharacterValid(font, c) == true) { // Remove offsets from patch fixed_t patchxofs = SHORT (font->font[c]->leftoffset) * dup * scale; cw = SHORT (font->font[c]->width) * dup; cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dup, &cw); V_DrawStretchyFixedPatch(cx + cxoff + patchxofs, cy + cyoff, scale, vscale, flags, font->font[c], colormap); cx += cw; } else { cx += fontspec.spacew; } } } } } fixed_t V_StringScaledWidth( fixed_t scale, fixed_t spacescale, fixed_t lfscale, INT32 flags, int fontno, const char *s) { INT32 hchw;/* half-width for centering */ INT32 dup; font_t *font; boolean uppercase; fixed_t cx; fixed_t right; fixed_t cw; int c; fixed_t fullwidth = 0; uppercase = !( flags & V_ALLOWLOWERCASE ); flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ font = &fontv[fontno]; fontspec_t fontspec; V_GetFontSpecification(fontno, flags, &fontspec); hchw = fontspec.chw >> 1; fontspec.chw <<= FRACBITS; fontspec.spacew <<= FRACBITS; #define Mul( id, scale ) ( id = FixedMul (scale, id) ) Mul (fontspec.chw, scale); Mul (fontspec.spacew, scale); Mul (fontspec.lfh, scale); Mul (fontspec.spacew, spacescale); Mul (fontspec.lfh, lfscale); #undef Mul if (( flags & V_NOSCALESTART )) { dup = vid.dup; hchw *= dup; fontspec.chw *= dup; fontspec.spacew *= dup; fontspec.lfh *= dup; } else { dup = 1; } cx = 0; right = 0; for (; ( c = *s ); ++s) { switch (c) { case '\n': cx = 0; break; default: if (( c & 0xF0 ) == 0x80 || c == V_STRINGDANCE) continue; if (uppercase) { c = toupper(c); } else if (V_CharacterValid(font, c - font->start) == false) { // Try the other case if it doesn't exist if (c >= 'A' && c <= 'Z') { c = tolower(c); } else if (c >= 'a' && c <= 'z') { c = toupper(c); } } c -= font->start; if (V_CharacterValid(font, c) == true) { cw = SHORT (font->font[c]->width) * dup; // How bunched dims work is by incrementing cx slightly less than a full character width. // This causes the next character to be drawn overlapping the previous. // We need to count the full width to get the rightmost edge of the string though. right = cx + (cw * scale); (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dup, &cw); cx += cw; } else cx += fontspec.spacew; } fullwidth = max(right, max(cx, fullwidth)); } return fullwidth; } // Modify a string to wordwrap at any given width. char * V_ScaledWordWrap( fixed_t w, fixed_t scale, fixed_t spacescale, fixed_t lfscale, INT32 flags, int fontno, const char *s) { INT32 hchw;/* half-width for centering */ INT32 dup; font_t *font; boolean uppercase; fixed_t cx; fixed_t right; fixed_t cw; int c; uppercase = !( flags & V_ALLOWLOWERCASE ); flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ font = &fontv[fontno]; fontspec_t fontspec; V_GetFontSpecification(fontno, flags, &fontspec); hchw = fontspec.chw >> 1; fontspec.chw <<= FRACBITS; fontspec.spacew <<= FRACBITS; #define Mul( id, scale ) ( id = FixedMul (scale, id) ) Mul (fontspec.chw, scale); Mul (fontspec.spacew, scale); Mul (fontspec.lfh, scale); Mul (fontspec.spacew, spacescale); Mul (fontspec.lfh, lfscale); #undef Mul if (( flags & V_NOSCALESTART )) { dup = vid.dup; hchw *= dup; fontspec.chw *= dup; fontspec.spacew *= dup; fontspec.lfh *= dup; } else { dup = 1; } cx = 0; right = 0; size_t reader = 0, writer = 0, startwriter = 0; fixed_t cxatstart = 0; size_t len = strlen(s) + 1; size_t potentialnewlines = 8; size_t sparenewlines = potentialnewlines; char *newstring = Z_Malloc(len + sparenewlines, PU_STATIC, NULL); for (; ( c = s[reader] ); ++reader, ++writer) { newstring[writer] = s[reader]; right = 0; switch (c) { case '\n': cx = 0; cxatstart = 0; startwriter = 0; break; default: if (( c & 0xF0 ) == 0x80 || c == V_STRINGDANCE) ; else { if (uppercase) { c = toupper(c); } else if (V_CharacterValid(font, c - font->start) == false) { // Try the other case if it doesn't exist if (c >= 'A' && c <= 'Z') { c = tolower(c); } else if (c >= 'a' && c <= 'z') { c = toupper(c); } } c -= font->start; if (V_CharacterValid(font, c) == true) { cw = SHORT (font->font[c]->width) * dup; // How bunched dims work is by incrementing cx slightly less than a full character width. // This causes the next character to be drawn overlapping the previous. // We need to count the full width to get the rightmost edge of the string though. right = cx + (cw * scale); (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dup, &cw); cx += cw; } else { cx += fontspec.spacew; cxatstart = cx; startwriter = writer; } } } // Start trying to wrap if presumed length exceeds the space we have on-screen. if (right && right > w) { if (startwriter != 0) { newstring[startwriter] = '\n'; cx -= cxatstart; cxatstart = 0; startwriter = 0; } else { if (sparenewlines == 0) { sparenewlines = (potentialnewlines *= 2); newstring = Z_Realloc(newstring, len + sparenewlines, PU_STATIC, NULL); } sparenewlines--; len++; newstring[writer++] = '\n'; // Over-write previous cx = cw; // Valid value in the only case right is currently set newstring[writer] = s[reader]; // Re-add } } } newstring[writer] = '\0'; return newstring; } // void V_DrawCenteredString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_StringWidth(string, option)/2; V_DrawString(x, y, option, string); } void V_DrawRightAlignedString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_StringWidth(string, option); V_DrawString(x, y, option, string); } void V_DrawCenteredSmallString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_SmallStringWidth(string, option)/2; V_DrawSmallString(x, y, option, string); } void V_DrawCenteredSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) { x -= (V_SmallStringWidth(string, option) / 2)<width); // this SHOULD always be 5 but I guess custom graphics exist. if (flags & V_NOSCALESTART) w *= vid.dup; if (num < 0) num = -num; // draw the number do { x -= (w-1); // Oni wanted their outline to intersect. V_DrawFixedPatch(x<width); // this SHOULD always be 5 but I guess custom graphics exist. if (flags & V_NOSCALESTART) w *= vid.dup; if (num < 0) num = -num; // draw the number do { x -= (w-1)*FRACUNIT; // Oni wanted their outline to intersect. V_DrawFixedPatch(x, y, FRACUNIT, flags, fontv[PINGNUM_FONT].font[num%10], colormap); num /= 10; } while (num); return x; } // Jaden: Draw a number using the position numbers. // void V_DrawRankNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits, const UINT8 *colormap) { INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width) - 1; if (flags & V_NOSCALESTART) w *= vid.dup; if (num < 0) num = -num; // draw the number do { x -= (w - 1); V_DrawFixedPatch(x << FRACBITS, y << FRACBITS, FRACUNIT, flags, fontv[PINGNUM_FONT].font[num % 10], colormap); num /= 10; } while (--digits); } // Find string width from cred_font chars // INT32 V_CreditStringWidth(const char *string) { INT32 c, w = 0; size_t i; // It's possible for string to be a null pointer if (!string) return 0; for (i = 0; i < strlen(string); i++) { c = toupper(string[i]) - CRED_FONTSTART; if (c < 0 || c >= CRED_FONTSIZE) w += 16; else w += fontv[CRED_FONT].font[c]->width; } return w; } // Draws a tallnum. Replaces two functions in y_inter and st_stuff void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num) { INT32 w = SHORT(fontv[TALLNUM_FONT].font[0]->width); boolean neg; if (flags & V_NOSCALESTART) w *= vid.dup; if ((neg = num < 0)) num = -num; // draw the number do { x -= w; V_DrawScaledPatch(x, y, flags, fontv[TALLNUM_FONT].font[num % 10]); num /= 10; } while (num); // draw a minus sign if necessary //if (neg) //V_DrawScaledPatch(x - w, y, flags, tallminus); // Tails } // Draws a number with a set number of digits. // Does not handle negative numbers in a special way, don't try to feed it any. void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) { INT32 w = fontv[TALLNUM_FONT].font[0]->width; if (flags & V_NOSCALESTART) w *= vid.dup; if (num < 0) num = -num; // draw the number do { x -= w; V_DrawScaledPatch(x, y, flags, fontv[TALLNUM_FONT].font[num % 10]); num /= 10; } while (--digits); } void V_DrawPaddedTallColorNumAtFixed(fixed_t x, fixed_t y, INT32 flags, INT32 num, INT32 digits, const UINT8 *colormap) { INT32 w = fontv[TALLNUM_FONT].font[0]->width; if (flags & V_NOSCALESTART) w *= vid.dup; if (num < 0) num = -num; // draw the number do { x -= w*FRACUNIT; V_DrawFixedPatch(x, y, FRACUNIT, flags, fontv[TALLNUM_FONT].font[num % 10], colormap); num /= 10; } while (--digits); } // Find string width from lt_font chars // INT32 V_LevelNameWidth(const char *string) { INT32 c, w = 0; size_t i; for (i = 0; i < strlen(string); i++) { if (string[i] & 0x80) continue; c = string[i] - LT_FONTSTART; if (c < 0 || c >= LT_FONTSIZE || !fontv[LT_FONT].font[c]) w += 12; else w += fontv[LT_FONT].font[c]->width; } return w; } // Find max height of the string // INT32 V_LevelNameHeight(const char *string) { INT32 c, w = 0; size_t i; for (i = 0; i < strlen(string); i++) { c = string[i] - LT_FONTSTART; if (c < 0 || c >= LT_FONTSIZE || !fontv[LT_FONT].font[c]) continue; if (fontv[LT_FONT].font[c]->height > w) w = fontv[LT_FONT].font[c]->height; } return w; } // // Find string width from hu_font chars // INT32 V_SubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0, lw = 0; INT32 spacewidth = 4, charwidth = 0; ssize_t i; if (length < 0) length = strlen(string); switch (option & V_SPACINGMASK) { case V_MONOSPACE: spacewidth = 8; /* FALLTHRU */ case V_OLDSPACING: charwidth = 8; break; case V_6WIDTHSPACE: spacewidth = 6; default: break; } for (i = 0; string[i] && i < length; i++) { c = string[i]; if (c == '\n') { w = max(w, lw); // width is based on widest line of text lw = 0; continue; } if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 continue; if (c < HU_FONTSTART) continue; // this is not a proper character. c = toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) lw += spacewidth; else lw += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); } w = max(w, lw); if (option & (V_NOSCALESTART|V_NOSCALEPATCH)) w *= vid.dup; return w; } // // Find string width from hu_font chars, 0.5x scale // INT32 V_SmallSubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0, lw = 0; INT32 spacewidth = 2, charwidth = 0; ssize_t i; if (length < 0) length = strlen(string); switch (option & V_SPACINGMASK) { case V_MONOSPACE: spacewidth = 4; /* FALLTHRU */ case V_OLDSPACING: charwidth = 4; break; case V_6WIDTHSPACE: spacewidth = 3; default: break; } for (i = 0; string[i] && i < length; i++) { c = string[i]; if (c == '\n') { w = max(w, lw); // width is based on widest line of text lw = 0; continue; } if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 continue; if (c < HU_FONTSTART) continue; // this is not a proper character. c = toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) lw += spacewidth; else lw += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width / 2); } w = max(w, lw); return w; } // // Find string width from tny_font chars // INT32 V_ThinSubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0, lw = 0; INT32 spacewidth = 2, charwidth = 0; boolean lowercase = (option & V_ALLOWLOWERCASE); ssize_t i; if (length < 0) length = strlen(string); switch (option & V_SPACINGMASK) { case V_MONOSPACE: spacewidth = 5; /* FALLTHRU */ case V_OLDSPACING: charwidth = 5; break; // Out of video flags, so we're reusing this for alternate charwidth instead /*case V_6WIDTHSPACE: spacewidth = 3;*/ default: break; } for (i = 0; string[i] && i < length; i++) { c = string[i]; if (c == '\n') { w = max(w, lw); // width is based on widest line of text lw = 0; continue; } if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 continue; if (c < HU_FONTSTART) continue; // this is not a proper character. if (!lowercase || !fontv[TINY_FONT].font[c-HU_FONTSTART]) c = toupper(c); c -= HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[TINY_FONT].font[c]) lw += spacewidth; else { lw += (charwidth ? charwidth : ((option & V_6WIDTHSPACE && i < (ssize_t)strlen(string)-1) ? max(1, fontv[TINY_FONT].font[c]->width-1) // Reuse this flag for the alternate bunched-up spacing : fontv[TINY_FONT].font[c]->width)); } } w = max(w, lw); return w; } // // Find string width from tny_font chars, 0.5x scale // /*INT32 V_SmallThinStringWidth(const char *string, INT32 option) { INT32 w = V_ThinStringWidth(string, option)<= 0x80 && (UINT8)c <= 0x8F) //color parsing! -Inuyasha 2.16.09 continue; c = toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) w += spacewidth; else w += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); } return max(i-1, 0); } boolean *heatshifter = NULL; INT32 lastheight = 0; INT32 heatindex[MAXSPLITSCREENPLAYERS] = {0, 0, 0, 0}; // // V_DoPostProcessor // // Perform a particular image postprocessing function. // void V_DoPostProcessor(INT32 view, INT32 param) { #if NUMSCREENS < 5 // do not enable image post processing for ARM, SH and MIPS CPUs (void)view; (void)type; (void)param; #else (void)param; // unused motion blur stuff INT32 yoffset, xoffset; #ifdef HWRENDER if (rendermode != render_soft) return; #endif if (view < 0 || view > 3 || view > r_splitscreen) return; camera_t *thiscam = &camera[view]; if (!thiscam->postimgflags) return; if ((view == 1 && r_splitscreen == 1) || view >= 2) yoffset = viewheight; else yoffset = 0; if ((view == 1 || view == 3) && r_splitscreen > 1) xoffset = viewwidth; else xoffset = 0; UINT8 *tmpscr = vid.screens[4]; UINT8 *srcscr = vid.screens[0]; if (thiscam->postimgflags & POSTIMG_WATER) { INT32 y; // Set disStart to a range from 0 to FINEANGLE, incrementing by 128 per tic angle_t disStart = (((leveltime-1)*128) + (R_GetTimeFrac(RTF_LEVEL) / (FRACUNIT/128))) & FINEMASK; INT32 newpix; INT32 sine; //UINT8 *transme = R_GetTranslucencyTable(tr_trans50); for (y = yoffset; y < yoffset+viewheight; y++) { sine = (FINESINE(disStart)*5)>>FRACBITS; newpix = abs(sine); if (sine < 0) { memcpy(&tmpscr[(y*vid.width)+xoffset+newpix], &srcscr[(y*vid.width)+xoffset], viewwidth-newpix); // Cleanup edge while (newpix) { tmpscr[(y*vid.width)+xoffset+newpix] = srcscr[(y*vid.width)+xoffset]; newpix--; } } else { memcpy(&tmpscr[(y*vid.width)+xoffset+0], &srcscr[(y*vid.width)+xoffset+sine], viewwidth-newpix); // Cleanup edge while (newpix) { tmpscr[(y*vid.width)+xoffset+viewwidth-newpix] = srcscr[(y*vid.width)+xoffset+(viewwidth-1)]; newpix--; } } /* * Unoptimized version * for (x = 0; x < vid.width; x++) * { * newpix = (x + sine); * * if (newpix < 0) * newpix = 0; * else if (newpix >= vid.width) * newpix = vid.width-1; * * tmpscrwater[y*vid.width + x] = srcscr[y*vid.width+newpix]; // *(transme + (srcscr[y*vid.width+x]<<8) + srcscr[y*vid.width+newpix]); }*/ disStart += 22;//the offset into the displacement map, increment each game loop disStart &= FINEMASK; //clip it to FINEMASK } UINT8 *tmp = tmpscr; tmpscr = srcscr; srcscr = tmp; } else if (thiscam->postimgflags & POSTIMG_HEAT) // Heat wave { INT32 y; // Make sure table is built if (heatshifter == NULL || lastheight != viewheight) { Z_Free(heatshifter); heatshifter = Z_Calloc(viewheight * sizeof(boolean), PU_STATIC, NULL); for (y = 0; y < viewheight; y++) { if (M_RandomChance(FRACUNIT/8)) // 12.5% heatshifter[y] = true; } heatindex[0] = heatindex[1] = heatindex[2] = heatindex[3] = 0; lastheight = viewheight; } for (y = yoffset; y < yoffset+viewheight; y++) { if (heatshifter[heatindex[view]++]) { // Shift this row of pixels to the right by 2 tmpscr[(y*vid.width)+xoffset] = srcscr[(y*vid.width)+xoffset]; memcpy(&tmpscr[(y*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset+vid.dup], viewwidth-vid.dup); } else memcpy(&tmpscr[(y*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset], viewwidth); heatindex[view] %= viewheight; } if (renderisnewtic) // This isn't interpolated... but how do you interpolate a one-pixel shift? { heatindex[view]++; heatindex[view] %= vid.height; } UINT8 *tmp = tmpscr; tmpscr = srcscr; srcscr = tmp; } /*if (thiscam->postimg & POSTIMG_MOTION) // Motion Blur! { INT32 x, y; // TODO: Add a postimg_param so that we can pick the translucency level... UINT8 *transme = transtables + ((param-1)<postimgflags & POSTIMG_FLIP) && !(thiscam->postimgflags & POSTIMG_MIRROR)) // Flip the screen upside-down { INT32 y, y2; for (y = yoffset, y2 = yoffset+viewheight - 1; y < yoffset+viewheight; y++, y2--) memcpy(&tmpscr[(y2*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset], viewwidth); UINT8 *tmp = tmpscr; tmpscr = srcscr; srcscr = tmp; } else if ((thiscam->postimgflags & POSTIMG_MIRROR) && !(thiscam->postimgflags & POSTIMG_FLIP)) // Flip the screen on the x axis { INT32 y, x, x2; for (y = yoffset; y < yoffset+viewheight; y++) for (x = xoffset, x2 = xoffset+((viewwidth)-1); x < xoffset+(viewwidth); x++, x2--) tmpscr[y*vid.width + x2] = srcscr[y*vid.width + x]; UINT8 *tmp = tmpscr; tmpscr = srcscr; srcscr = tmp; } else if ((thiscam->postimgflags & POSTIMG_MIRROR) && (thiscam->postimgflags & POSTIMG_FLIP)) // Flip the screen upside-down and on the x axis { INT32 y, x; for (y = yoffset; y < yoffset + viewheight; y++) for (x = xoffset; x < xoffset + viewwidth; x++) tmpscr[((yoffset + viewheight - 1 - y) * vid.width) + xoffset + viewwidth - (x - xoffset) - 1] = srcscr[(y * vid.width) + x]; UINT8 *tmp = tmpscr; tmpscr = srcscr; srcscr = tmp; } VID_BlitLinearScreen(srcscr+vid.width*yoffset+xoffset, tmpscr+vid.width*yoffset+xoffset, viewwidth, viewheight, vid.width, vid.width); #endif } // Generates a RGB565 color look-up table void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors) { size_t palsize = (sizeof(RGBA_t) * 256); if (!lut->init || memcmp(lut->palette, palette, palsize)) { size_t i; lut->init = true; memcpy(lut->palette, palette, palsize); for (i = 0; i < sizeof(lut->table)/sizeof(*lut->table); i++) lut->table[i] = 0xFFFF; if (makecolors) { UINT8 r, g, b; for (r = 0; r < 0xFF; r++) for (g = 0; g < 0xFF; g++) for (b = 0; b < 0xFF; b++) { i = CLUTINDEX(r, g, b); if (lut->table[i] == 0xFFFF) lut->table[i] = NearestPaletteColor(r, g, b, palette); } } } } UINT8 GetColorLUT(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b) { INT32 i = CLUTINDEX(r, g, b); if (lut->table[i] == 0xFFFF) lut->table[i] = NearestPaletteColor(r, g, b, lut->palette); return lut->table[i]; } UINT8 GetColorLUTDirect(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b) { INT32 i = CLUTINDEX(r, g, b); return lut->table[i]; } // V_Init // old software stuff, buffers are allocated at video mode setup // here we set the screens[x] pointers accordingly // WARNING: called at runtime (don't init cvar here) void V_Init(void) { INT32 i; INT32 screensize = vid.rowbytes * vid.height; for (i = 0; i < NUMSCREENS; i++) { if (vid.screens[i]) { #if defined(__SSE__) aligned_free(vid.screens[i]); #else free(vid.screens[i]); #endif } vid.screens[i] = NULL; } // start address of NUMSCREENS * width*height vidbuffers if (screensize > 0) { for (i = 0; i < NUMSCREENS; i++) { // we need to allocate these relative to their cpu restrictions to not trigger segfaults // TODO: add support for sve and neon #if defined(__SSE__) while (screensize & 15) screensize++; vid.screens[i] = aligned_alloc(16, screensize); #else vid.screens[i] = malloc(screensize); #endif memset(vid.screens[i], 0, screensize); } } #ifdef DEBUG CONS_Debug(DBG_RENDER, "V_Init done:\n"); for (i = 0; i < NUMSCREENS; i++) CONS_Debug(DBG_RENDER, " vid.screens[%d] = %x\n", i, screens[i]); #endif } void V_Recalc(void) { // scale 1,2,3 times in x and y the patches for the menus and overlays... // calculated once and for all, used by routines in v_video.c and v_draw.c // Set dup based on width or height, whichever is less if (((vid.width*FRACUNIT) / BASEVIDWIDTH) < ((vid.height*FRACUNIT) / BASEVIDHEIGHT)) { vid.dup = vid.width / BASEVIDWIDTH; vid.fdup = (vid.width*FRACUNIT) / BASEVIDWIDTH; } else { vid.dup = vid.height / BASEVIDHEIGHT; vid.fdup = (vid.height*FRACUNIT) / BASEVIDHEIGHT; } vid.udup = vid.dup; if (loaded_config // this could use a better name, since it is more and indicator that early startup is done and its safe to do sketchy shit now :chaosleep: && (vid.width > 720) && (vid.height > 1280)) // ehhhh well this thing has so many issues, so ill lock it to higher resolutions instead { vid.dup = FixedDiv(vid.dup, cv_highreshudscale.value); vid.fdup = FixedDiv(vid.fdup, cv_highreshudscale.value); } vid.scaledwidth = (vid.width/vid.dup); vid.scaledheight = (vid.height/vid.dup); vid.meddup = (UINT8)(vid.dup >> 1) + 1; vid.smalldup = (UINT8)(vid.dup / 3) + 1; }