// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2024 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. // // 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 #include #include #include "doomdef.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 "r_fps.h" #include "k_dialogue.h" // K_GetDialogueSlide #include "i_video.h" // rendermode #include "z_zone.h" #include "m_misc.h" #include "m_random.h" #include "doomstat.h" #include "hwr2/blendmode.hpp" #ifdef HWRENDER #include "hardware/hw_glob.h" #endif // SRB2Kart #include "k_hud.h" #include "k_boss.h" #include "i_time.h" #include "v_draw.hpp" using namespace srb2; // Each screen is [vid.width*vid.height]; UINT8 *screens[5]; // screens[0] = main display window // screens[1] = back screen, alternative blitting // screens[2] = screenshot buffer, gif movie buffer // screens[3] = fade screen start // screens[4] = fade screen end, postimage tempoarary buffer #define huecoloursteps 4 CV_PossibleValue_t hue_cons_t[] = {{0, "MIN"}, {(huecoloursteps*6)-1, "MAX"}, {0, NULL}}; CV_PossibleValue_t constextsize_cons_t[] = { {V_NOSCALEPATCH, "Small"}, {V_SMALLSCALEPATCH, "Medium"}, {V_MEDSCALEPATCH, "Large"}, {0, "Huge"}, {0, NULL}}; // local copy of the palette for V_GetColor() RGBA_t *pLocalPalette = NULL; RGBA_t *pMasterPalette = NULL; RGBA_t *pGammaCorrectedPalette = NULL; hwr2::Twodee srb2::g_2d; static size_t currentPaletteSize; static UINT8 softwaretranstohwr[11] = { 0, 25, 51, 76,102,127,153,178,204,229,255}; /* 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 (con_startup_loadprogress < LOADED_CONFIG) return false; #define diffcons(cv) (cv.value != atoi(cv.defaultvalue)) doinggamma = diffcons(cv_globalgamma); #define gammascale 8 globalgammamul = (cv_globalgamma.value ? ((255 - (gammascale*abs(cv_globalgamma.value)))/255.0) : 1.0); globalgammaoffs = ((cv_globalgamma.value > 0) ? ((gammascale*cv_globalgamma.value)/255.0) : 0.0); 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_rgamma) || diffcons(cv_ygamma) || diffcons(cv_ggamma) || diffcons(cv_cgamma) || diffcons(cv_bgamma) || diffcons(cv_mgamma)) // 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_rgamma.value, working[1][0][0]); dohue(cv_yhue.value, cv_ygamma.value, working[1][1][0]); dohue(cv_ghue.value, cv_ggamma.value, working[0][1][0]); dohue(cv_chue.value, cv_cgamma.value, working[0][1][1]); dohue(cv_bhue.value, cv_bgamma.value, working[0][0][1]); dohue(cv_mhue.value, cv_mgamma.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_rgamma.value, 1, 0, 0); docvsat(cv_ysaturation, cv_yhue.value, cv_ygamma.value, 1, 1, 0); docvsat(cv_gsaturation, cv_ghue.value, cv_ggamma.value, 0, 1, 0); docvsat(cv_csaturation, cv_chue.value, cv_cgamma.value, 0, 1, 1); docvsat(cv_bsaturation, cv_bhue.value, cv_bgamma.value, 0, 0, 1); docvsat(cv_msaturation, cv_mhue.value, cv_mgamma.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 = static_cast(Z_Malloc(sizeof (*pMasterPalette)*palsize, PU_STATIC, NULL)); if (Cubeapply) pLocalPalette = static_cast(Z_Malloc(sizeof (*pLocalPalette)*palsize, PU_STATIC, NULL)); else pLocalPalette = pMasterPalette; pGammaCorrectedPalette = static_cast(Z_Malloc(sizeof (*pGammaCorrectedPalette)*palsize, PU_STATIC, NULL)); pal = static_cast(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; pGammaCorrectedPalette[i].rgba = V_GammaDecode(pMasterPalette[i].rgba); if (!Cubeapply) continue; V_CubeApply(&pGammaCorrectedPalette[i]); pLocalPalette[i].rgba = V_GammaEncode(pGammaCorrectedPalette[i].rgba); } } 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.0) working[0][q] = 255.0; else if (working[0][q] < 0.0) 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); strlcpy(palname, newpal, 9); 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); } extern "C" { void CV_palette_OnChange(void); void CV_palette_OnChange(void) { if (con_startup_loadprogress < LOADED_CONFIG) return; // recalculate Color Cube V_ReloadPalette(); V_SetPalette(0); } }; // extern "C" #if defined (__GNUC__) && defined (__i386__) && !defined (NOASM) && !defined (__APPLE__) && !defined (NORUSEASM) void VID_BlitLinearScreen_ASM(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes, size_t destrowbytes); #define HAVE_VIDCOPY #endif extern "C" { void CV_constextsize_OnChange(void); void CV_constextsize_OnChange(void) { if (!con_startup) con_recalc = true; } }; // extern "C" // -------------------------------------------------------------------------- // Copy a rectangular area from one bitmap to another (8bpp) // -------------------------------------------------------------------------- void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes, size_t destrowbytes) { #ifdef HAVE_VIDCOPY VID_BlitLinearScreen_ASM(srcptr,destptr,width,height,srcrowbytes,destrowbytes); #else if ((srcrowbytes == destrowbytes) && (srcrowbytes == (size_t)width)) M_Memcpy(destptr, srcptr, srcrowbytes * height); else { while (height--) { M_Memcpy(destptr, srcptr, width); destptr += destrowbytes; srcptr += srcrowbytes; } } #endif } void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 dupy) { // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx INT32 screenwidth = vid.width; INT32 screenheight = vid.height; INT32 basewidth = BASEVIDWIDTH * dupx; INT32 baseheight = BASEVIDHEIGHT * dupy; SINT8 player = R_GetViewNumber(); if (r_splitscreen > 0) { if (options & V_SPLITSCREEN) { screenheight /= 2; baseheight /= 2; if (r_splitscreen > 1) { screenwidth /= 2; basewidth /= 2; } } } else if ((options & (V_SLIDEIN|V_SNAPTOBOTTOM)) == (V_SLIDEIN|V_SNAPTOBOTTOM)) { INT32 slide = K_GetDialogueSlide(51 * FRACUNIT); if (slide) { *y -= FixedMul(slide, dupy); } } if (vid.width != (BASEVIDWIDTH * dupx)) { if (options & V_SNAPTORIGHT) *x += (screenwidth - basewidth); else if (!(options & V_SNAPTOLEFT)) *x += (screenwidth - basewidth) / 2; } if (vid.height != (BASEVIDHEIGHT * dupy)) { 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)) { if (st_fadein < FRACUNIT) { if ((options & (V_SNAPTORIGHT|V_SNAPTOLEFT|V_SPLITSCREEN)) != 0) { boolean slidefromright = false; const fixed_t offsetAmount = (screenwidth * FRACUNIT/2); INT32 offset = (offsetAmount - FixedMul(offsetAmount, st_fadein)) / FRACUNIT; if (r_splitscreen > 1) { if (player & 1) slidefromright = true; } if (options & V_SNAPTORIGHT) slidefromright = true; else if (options & V_SNAPTOLEFT) slidefromright = false; if (slidefromright == true) { offset = -offset; } *x -= offset; } else { const fixed_t offsetAmount = (screenheight * FRACUNIT/2); INT32 offset = (offsetAmount - FixedMul(offsetAmount, st_fadein)) / FRACUNIT; if (options & V_SNAPTOBOTTOM) { offset = -offset; } *y -= offset; } } } } static cliprect_t cliprect = {0}; const cliprect_t *V_GetClipRect(void) { if (cliprect.enabled == false) { return NULL; } return &cliprect; } 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 dupx = vid.dupx; fixed_t dupy = vid.dupy; if (flags & V_SCALEPATCHMASK) { switch ((flags & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT) { case 1: // V_NOSCALEPATCH dupx = dupy = 1; break; case 2: // V_SMALLSCALEPATCH dupx = vid.smalldupx; dupy = vid.smalldupy; break; case 3: // V_MEDSCALEPATCH dupx = vid.meddupx; dupy = vid.meddupy; break; default: break; } } dupx = dupy = (dupx < dupy ? dupx : dupy); x = FixedMul(x, dupx); y = FixedMul(y, dupy); w = FixedMul(w, dupx); h = FixedMul(h, dupy); if (!(flags & V_SCALEPATCHMASK)) { V_AdjustXYWithSnap(&x, &y, flags, dupx, dupy); } } if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x > vid.width) { x = vid.width; w = 0; } if (y > vid.height) { y = vid.height; 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); */ } void V_ClearClipRect(void) { cliprect.enabled = false; } void V_SaveClipRect(cliprect_t *copy) { *copy = cliprect; } void V_RestoreClipRect(const cliprect_t *copy) { cliprect = *copy; } 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}; static const UINT8 *v_colormap = NULL; static const UINT8 *v_translevel = NULL; static inline UINT8 standardpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs) { (void)dest; return source[ofs>>FRACBITS]; } static inline UINT8 mappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs) { (void)dest; return *(v_colormap + source[ofs>>FRACBITS]); } static inline UINT8 translucentpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs) { return *(v_translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff)); } static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs) { return *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff)); } UINT32 V_GetHUDTranslucency(INT32 scrn) { if (scrn & V_SLIDEIN) { return 10; } if (scrn & V_SPLITSCREEN) { return FixedMul(10, st_fadein); } return st_translucency; } static UINT32 V_GetAlphaLevel(INT32 scrn) { switch (scrn & V_ALPHAMASK) { case V_HUDTRANSHALF: return hudminusalpha[V_GetHUDTranslucency(scrn)]; case V_HUDTRANS: return 10 - V_GetHUDTranslucency(scrn); case V_HUDTRANSDOUBLE: return hudplusalpha[V_GetHUDTranslucency(scrn)]; default: return (scrn & V_ALPHAMASK) >> V_ALPHASHIFT; } } // 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) { UINT32 alphalevel, blendmode; fixed_t vdup; INT32 dupx, dupy; fixed_t pwidth; // patch 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 if ((blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT))) blendmode++; // realign to constants if ((alphalevel = V_GetAlphaLevel(scrn)) >= 10) return; dupx = vid.dupx; dupy = vid.dupy; if (scrn & V_SCALEPATCHMASK) switch ((scrn & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT) { case 1: // V_NOSCALEPATCH dupx = dupy = 1; break; case 2: // V_SMALLSCALEPATCH dupx = vid.smalldupx; dupy = vid.smalldupy; break; case 3: // V_MEDSCALEPATCH dupx = vid.meddupx; dupy = vid.meddupy; break; default: break; } // only use one dup, to avoid stretching (har har) dupx = dupy = (dupx < dupy ? dupx : dupy); vdup = FixedMul(dupx<width - patch->leftoffset)<leftoffset<height - patch->topoffset)<topoffset<>= FRACBITS; y >>= FRACBITS; } else { x = FixedMul(x,dupx<>= FRACBITS; y >>= FRACBITS; // Center it if necessary if (!(scrn & V_SCALEPATCHMASK)) { V_AdjustXYWithSnap(&x, &y, scrn, dupx, dupy); } } if (pscale != FRACUNIT) // scale width properly { pwidth = patch->width<>= FRACBITS; } else pwidth = patch->width * dupx; float fdupy = FIXED_TO_FLOAT(vdup); float fx = x; float fy = y; float fx2 = fx + pwidth; float fy2 = fy + std::round(static_cast(patch->height) * fdupy); float falpha = 1.f; float umin = 0.f; float umax = 1.f; float vmin = 0.f; float vmax = 1.f; // flip UVs if (scrn & V_FLIP) { umin = 1.f - umin; umax = 1.f - umax; } if (scrn & V_VFLIP) { vmin = 1.f - vmin; vmax = 1.f - vmax; } if (alphalevel > 0 && alphalevel <= 10) { falpha = (10 - alphalevel) / 10.f; } hwr2::BlendMode blend = hwr2::BlendMode::kAlphaTransparent; switch (blendmode) { case AST_MODULATE: blend = hwr2::BlendMode::kModulate; break; case AST_ADD: blend = hwr2::BlendMode::kAdditive; break; // Note: SRB2 has these blend modes flipped compared to GL and Vulkan. // SRB2's Subtract is Dst - Src. OpenGL is Src - Dst. And vice versa for reverse. // Twodee will use the GL definitions. case AST_SUBTRACT: blend = hwr2::BlendMode::kReverseSubtractive; break; case AST_REVERSESUBTRACT: blend = hwr2::BlendMode::kSubtractive; break; default: blend = hwr2::BlendMode::kAlphaTransparent; break; } auto builder = g_2d.begin_quad(); builder .patch(patch) .rect(fx, fy, fx2 - fx, fy2 - fy) .flip((scrn & V_FLIP) > 0) .vflip((scrn & V_VFLIP) > 0) .color(1, 1, 1, falpha) .blend(blend) .colormap(colormap); if (clip && clip->enabled) { builder.clip(clip->left, clip->top, clip->right, clip->bottom); } builder.done(); } // 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, static_cast(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; if (x < 0 || x + width > vid.width || y < 0 || y + height > vid.height || (unsigned)scrn > 4) I_Error("Bad V_DrawBlock"); dest = screens[scrn] + y*vid.width + x; deststop = screens[scrn] + vid.rowbytes * vid.height; while (height--) { M_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_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { const cliprect_t *clip = V_GetClipRect(); if (rendermode == render_none) return; #ifdef HWRENDER //if (rendermode != render_soft && !con_startup) // Not this again if (rendermode == render_opengl) { HWR_DrawFill(x, y, w, h, c); return; } #endif UINT32 alphalevel; if ((alphalevel = V_GetAlphaLevel(c)) >= 10) return; if (!(c & V_NOSCALESTART)) { INT32 dupx = vid.dupx, dupy = vid.dupy; if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) { w = vid.width; h = vid.height; } else { x *= dupx; y *= dupy; w *= dupx; h *= dupy; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } } 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; c &= 255; RGBA_t color = pMasterPalette[c]; UINT8 r = (color.rgba & 0xFF); UINT8 g = (color.rgba & 0xFF00) >> 8; UINT8 b = (color.rgba & 0xFF0000) >> 16; if (clip && clip->enabled) { int x2 = std::min(x + w, clip->right); int y2 = std::min(y + h, clip->bottom); if (x < clip->left) x = clip->left; if (y < clip->top) y = clip->top; w = std::max(0, x2 - x); h = std::max(0, y2 - y); } g_2d.begin_quad() .patch(nullptr) .color(r / 255.f, g / 255.f, b / 255.f, (10 - alphalevel) / 10.f) .rect(x, y, w, h) .done(); } static UINT32 V_GetHWConsBackColor(void) { UINT32 hwcolor; switch (cons_backcolor.value) { case 0: hwcolor = 0xffffff00; break; // White case 1: hwcolor = 0x80808000; break; // Black case 2: hwcolor = 0xdeb88700; break; // Sepia case 3: hwcolor = 0x40201000; break; // Brown case 4: hwcolor = 0xfa807200; break; // Pink case 5: hwcolor = 0xff000000; break; // Red case 6: hwcolor = 0xff800000; break; // Orange case 7: hwcolor = 0xdaa52000; break; // Gold case 8: hwcolor = 0xffdd0000; break; // Yellow case 9: hwcolor = 0xc5e80000; break; // Peridot case 10: hwcolor = 0x00800000; break; // Green case 11: hwcolor = 0x15f2b000; break; // Aquamarine case 12: hwcolor = 0x00ffff00; break; // Cyan case 13: hwcolor = 0x4682b400; break; // Steel case 14: hwcolor = 0x0000ff00; break; // Blue case 15: hwcolor = 0x9844ff00; break; // Purple case 16: hwcolor = 0xff00ff00; break; // Magenta case 17: hwcolor = 0xee82ee00; break; // Lavender case 18: hwcolor = 0xf570a500; break; // Rose // Default green default: hwcolor = 0x00800000; break; } return hwcolor; } // THANK YOU MPC!!! // and thanks toaster for cleaning it up. void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { 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 = V_GetAlphaLevel(c)) >= 10) return; if (!(c & V_NOSCALESTART)) { INT32 dupx = vid.dupx, dupy = vid.dupy; x *= dupx; y *= dupy; w *= dupx; h *= dupy; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } 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; c &= 255; UINT32 hwcolor = V_GetHWConsBackColor(); float r = ((hwcolor & 0xFF000000) >> 24) / 255.f; float g = ((hwcolor & 0xFF0000) >> 16) / 255.f; float b = ((hwcolor & 0xFF00) >> 8) / 255.f; float a = 0.5f; // alphalevel is unused in GL?? g_2d.begin_quad() .rect(x, y, w, h) .blend(hwr2::BlendMode::kAlphaTransparent) .color(r, g, b, a) .done(); } // // 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) { INT32 w, h; if (rendermode == render_none) return; #ifdef HWRENDER if (rendermode == render_opengl) { HWR_DrawDiag(x, y, wh, c); return; } #endif if (!(c & V_NOSCALESTART)) { INT32 dupx = vid.dupx, dupy = vid.dupy; x *= dupx; y *= dupy; wh *= dupx; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } 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) { w = vid.width - x; } if (y + w > vid.height) h = vid.height - y; if (h > w) h = w; c &= 255; { auto builder = g_2d.begin_verts(); const RGBA_t color = pMasterPalette[c]; const float r = ((color.rgba & 0xFF000000) >> 24) / 255.f; const float g = ((color.rgba & 0xFF0000) >> 16) / 255.f; const float b = ((color.rgba & 0xFF00) >> 8) / 255.f; const float a = 1.f; builder.color(r, g, b, a); builder .vert(x, y) .vert(x + wh, y + wh) .vert(x, y + wh) .done(); } } // // 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) { 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 dupx = vid.dupx, dupy = vid.dupy; x *= dupx; y *= dupy; w *= dupx; h *= dupy; // Center it if necessary V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } 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; float r; float g; float b; float a; hwr2::BlendMode blendmode; if (color & 0xFF00) { // Historical COLORMAP fade // In Ring Racers this is a Mega Drive style per-channel fade (though it'd probably be cool in SRB2 too) // HWR2 will implement as a rev-subtractive rect because colormaps aren't possible in hardware float fstrength = std::clamp(strength / 31.f, 0.f, 1.f); r = std::clamp((fstrength - (0.f / 3.f)) * 3.f, 0.f, 1.f); g = std::clamp((fstrength - (1.f / 3.f)) * 3.f, 0.f, 1.f); b = std::clamp((fstrength - (2.f / 3.f)) * 3.f, 0.f, 1.f); a = 1; blendmode = hwr2::BlendMode::kReverseSubtractive; } else { // Historically TRANSMAP fade // This is done by modulative (transparent) blend to the given palette color. byteColor_t bc = V_GetColor(color).s; r = bc.red / 255.f; g = bc.green / 255.f; b = bc.blue / 255.f; a = softwaretranstohwr[std::clamp(static_cast(strength), 0, 10)] / 255.f; blendmode = hwr2::BlendMode::kAlphaTransparent; } g_2d.begin_quad() .blend(blendmode) .color(r, g, b, a) .rect(x, y, w, h) .done(); } // // 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 dupx; INT32 dupy; size_t size; size_t lflatsize; #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; break; case 1048576: // 1024x1024 lump lflatsize = 1024; break; case 262144:// 512x512 lump lflatsize = 512; break; case 65536: // 256x256 lump lflatsize = 256; break; case 16384: // 128x128 lump lflatsize = 128; break; case 1024: // 32x32 lump lflatsize = 32; break; case 256: // 16x16 lump lflatsize = 16; break; case 64: // 8x8 lump lflatsize = 8; break; default: // 64x64 lump lflatsize = 64; break; } float fsize = lflatsize; dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy); g_2d.begin_verts() .flat(flatnum) .vert(x * dupx, y * dupy, 0, 0) .vert(x * dupx + w * dupx, y * dupy, w / fsize, 0) .vert(x * dupx + w * dupx, y * dupy + h * dupy, w / fsize, h / fsize) .vert(x * dupx, y * dupy, 0, 0) .vert(x * dupx + w * dupx, y * dupy + h * dupy, w / fsize, h / fsize) .vert(x * dupx, y * dupy + h * dupy, 0, h / fsize) .done(); } // // V_DrawPatchFill // void V_DrawPatchFill(patch_t *pat) { INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy); INT32 x, y, pw = pat->width * dupz, ph = pat->height * dupz; for (x = 0; x < vid.width; x += pw) { for (y = 0; y < vid.height; y += ph) V_DrawScaledPatch(x, y, V_NOSCALESTART, pat); } } void V_DrawVhsEffect(boolean rewind) { static fixed_t upbary = 100, downbary = 150; UINT8 *buf = screens[0], *tmp = screens[4]; UINT16 y; UINT32 x, pos = 0; UINT8 *normalmapstart = ((UINT8 *)transtables + (8<>1; if (rewind) V_DrawVhsEffect(false); // experimentation upbary -= FixedMul(vid.dupy * (rewind ? 3 : 1.8f), renderdeltatics); downbary += FixedMul(vid.dupy * (rewind ? 2 : 1), renderdeltatics); if (upbary < -barsize) upbary = vid.height; if (downbary > vid.height) downbary = -barsize; for (y = 0; y < vid.height; y+=2) { thismapstart = normalmapstart; offs = 0; if (y >= upbary && y < upbary+barsize) { thismapstart -= (2<= downbary && y < downbary+barsize) { thismapstart -= (2<= vid.height-2 && offs > 0) offs = 0; for (x = pos+vid.rowbytes*2; 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.rowbytes*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 float r; float g; float b; float a; hwr2::BlendMode blendmode; if (color & 0xFF00) { // Historical COLORMAP fade // In Ring Racers this is a Mega Drive style per-channel fade (though it'd probably be cool in SRB2 too) // HWR2 will implement as a rev-subtractive rect because colormaps aren't possible in hardware float fstrength = std::clamp(strength / 31.f, 0.f, 1.f); r = std::clamp((fstrength - (0.f / 3.f)) * 3.f, 0.f, 1.f); g = std::clamp((fstrength - (1.f / 3.f)) * 3.f, 0.f, 1.f); b = std::clamp((fstrength - (2.f / 3.f)) * 3.f, 0.f, 1.f); a = 1; blendmode = hwr2::BlendMode::kReverseSubtractive; } else { // Historically TRANSMAP fade // This is done by modulative (transparent) blend to the given palette color. byteColor_t bc = V_GetColor(color).s; r = bc.red / 255.f; g = bc.green / 255.f; b = bc.blue / 255.f; a = softwaretranstohwr[std::clamp(static_cast(strength), 0, 10)] / 255.f; blendmode = hwr2::BlendMode::kAlphaTransparent; } g_2d.begin_quad() .blend(blendmode) .color(r, g, b, a) .rect(0, 0, vid.width, vid.height) .done(); } lighttable_t *V_LoadCustomFadeMap(const char *lump) { lumpnum_t lumpnum = LUMPERROR; lighttable_t *clm = NULL; if (lump != NULL) lumpnum = W_GetNumForName(lump); else return NULL; if (lumpnum != LUMPERROR) { clm = static_cast(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8)); W_ReadLump(lumpnum, clm); return clm; } return NULL; } const UINT8 *V_OffsetIntoFadeMap(const lighttable_t *clm, UINT8 strength) { return ((const UINT8 *)clm + strength*256); } // // 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 // TODO: fix this for Twodee { lighttable_t *clm = V_LoadCustomFadeMap(lump); if (clm != NULL) { const UINT8 *fadetable = V_OffsetIntoFadeMap(clm, strength); const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height; UINT8 *buf = 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) { UINT32 hwcolor = V_GetHWConsBackColor(); #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode == render_opengl) { HWR_DrawConsoleBack(hwcolor, plines); return; } #endif float r = ((hwcolor & 0xFF000000) >> 24) / 255.f; float g = ((hwcolor & 0xFF0000) >> 16) / 255.f; float b = ((hwcolor & 0xFF00) >> 8) / 255.f; float a = 0.5f; g_2d.begin_quad() .rect(0, 0, vid.width, plines) .blend(hwr2::BlendMode::kAlphaTransparent) .color(r, g, b, a) .done(); } // // Invert the entire screen, for Encore fades // void V_EncoreInvertScreen(void) { #ifdef HWRENDER if (rendermode != render_soft && rendermode != render_none) { HWR_EncoreInvertScreen(); return; } #endif g_2d.begin_quad() .blend(hwr2::BlendMode::kInvertDest) .color(1, 1, 1, 1) .rect(0, 0, vid.width, vid.height) .done(); } // 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) { 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.width/vid.dupx))/2, BASEVIDHEIGHT-boxheight, (vid.width/vid.dupx),boxheight, (color-256)|V_SNAPTOBOTTOM); return; } boxheight *= vid.dupy; if (color == INT32_MAX) color = cons_backcolor.value; UINT32 hwcolor = V_GetHWConsBackColor(); #ifdef HWRENDER if (rendermode == render_opengl) { HWR_DrawTutorialBack(hwcolor, boxheight); return; } #endif float r = ((color & 0xFF000000) >> 24) / 255.f; float g = ((color & 0xFF0000) >> 16) / 255.f; float b = ((color & 0xFF00) >> 8) / 255.f; float a = (color == 0 ? 0xC0 : 0x80) / 255.f; // make black darker, like software INT32 real_boxheight = (boxheight * 4) + (boxheight / 2) * 5; g_2d.begin_quad() .rect(0, vid.height - real_boxheight, vid.width, real_boxheight) .color(r, g, b, a) .done(); } // 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_DrawCharacterScaled( fixed_t x, fixed_t y, fixed_t scale, INT32 flags, int fontno, int c, UINT8 *colormap) { font_t *font = &fontv[fontno]; boolean notColored = false; const boolean uppercase = ((flags & V_FORCEUPPERCASE) == V_FORCEUPPERCASE); flags &= ~(V_FLIP); /* These two (V_FORCEUPPERCASE) share a bit. */ if (colormap == NULL) { colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); } notColored = (colormap == NULL); flags &= ~(V_CHARCOLORMASK | V_PARAMMASK); 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) == false) { return; } if (notColored == true) { if (( c & 0xF0 ) == 0x80) { colormap = V_GetStringColormap( ( ( c & 0x7f ) << V_CHARCOLORSHIFT ) & V_CHARCOLORMASK ); } } V_DrawFixedPatch( x, y, scale, flags, font->font[c], colormap ); } void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercase) { // Backwards compatibility if (lowercase == false) { c |= V_FORCEUPPERCASE; } V_DrawCharacterScaled( x << FRACBITS, y << FRACBITS, FRACUNIT, (c & ~V_PARAMMASK), HU_FONT, (c & V_PARAMMASK), NULL ); } void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercase, UINT8 *colormap) { // Backwards compatibility if (lowercase == false) { c |= V_FORCEUPPERCASE; } V_DrawCharacterScaled( x << FRACBITS, y << FRACBITS, FRACUNIT >> 1, (c & ~V_PARAMMASK), HU_FONT, (c & V_PARAMMASK), colormap ); } template static INT32 Internal_TitleCardStringOffset(const char *str, boolean p4) { int bg_font = GTOL_FONT; int fg_font = GTFN_FONT; if (p4) { bg_font = GTOL4_FONT; fg_font = GTFN4_FONT; } INT32 xoffs = 0; const char *ch = str; char c; patch_t *pp; // Returns true if it reached the end, false if interrupted. auto scan = [&](auto keep_going) { for (;;ch++) { if (*ch == '\n') { xoffs = 0; return false; } if (!keep_going(*ch)) { break; } 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[bg_font].font[(INT32)c]) { xoffs += p4 ? 5 : 10; continue; } pp = fontv[fg_font].font[(INT32)c]; xoffs += pp->width - (p4 ? 3 : 5); } return true; }; do { // For the sake of centering, don't count spaces or // punctuation at each end of a line. // TODO: This should ideally be more sophisticated: // - Check patch width directly for monospace or // punctuation that isn't necessarily thin. // - Apply to all centered string drawing. if constexpr (Centered) { // Count leading fluff if (!scan([](int c) { return c && !isalnum(c); })) { continue; } if (!*ch) { // ALL fluff, so center it normally. break; } // xoffs gets halved later, which centers the // string. If we don't want leading fluff to push // everything to the right, its full width needs // to be subtracted, so it's doubled here to // cancel out the division. xoffs *= 2; INT32 trim = -1; bool reached_end = scan( [&trim, &xoffs](int c) { if (isalnum(c)) { trim = -1; } else if (trim < 0) { trim = xoffs; } return c; } ); // Discount trailing fluff if (reached_end && trim >= 0) { xoffs = trim; } } else { scan([](int c) { return c; }); } } while (*(ch++)); if constexpr (Centered) { return xoffs / 2; } else { return xoffs; } } // V_TitleCardStringWidth // Get the string's width using the titlecard font. INT32 V_TitleCardStringWidth(const char *str, boolean p4) { return Internal_TitleCardStringOffset(str, p4); } // V_CenteredTitleCardStringOffset // Subtract this offset from an X coordinate to center the string around that point. INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4) { return Internal_TitleCardStringOffset(str, p4); } // V_DrawTitleCardStringFixed. // see v_video.h's prototype for more information. // void V_DrawTitleCardStringFixed(fixed_t x, fixed_t y, fixed_t scale, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4) { int bg_font = GTOL_FONT; int fg_font = GTFN_FONT; if (p4) { bg_font = GTOL4_FONT; fg_font = GTFN4_FONT; } 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 * scale; // Account for patch width... if (flags & V_SNAPTORIGHT) { x -= V_TitleCardStringWidth(str, p4) * scale; } for (;;ch++, i++) { scalex = FRACUNIT; offs = 0; let_time = timer - i; flipflag = 0; if (!*ch) break; if (*ch == '\n') { xoffs = x; yoffs += (p4 ? 18 : 32) * scale; 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[fg_font].font[(INT32)c]) { xoffs += (p4 ? 5 : 10) * scale; continue; } ol = fontv[bg_font].font[(INT32)c]; pp = fontv[fg_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 = std::min(360 + 90, let_time*41) * ANG1; scalex = FINESINE(fakeang>>ANGLETOFINESHIFT); } else if (!bossmode && let_time > threshold) { // Make letters disappear... let_time -= threshold; fakeang = std::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) + offs, (y+yoffs), FixedMul(abs(scalex), scale), scale, flags|flipflag, ol, NULL); V_DrawStretchyFixedPatch((x + xoffs) + offs, (y+yoffs), FixedMul(abs(scalex), scale), scale, flags|flipflag, pp, NULL); } xoffs += (pp->width - (p4 ? 3 : 5)) * scale; } } static inline fixed_t FixedCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)scale; (void)hchw; (void)dupx; (*cwp) = chw; return 0; } static inline fixed_t VariableCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul ((*cwp) << FRACBITS, scale); return 0; } static inline fixed_t CenteredCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, 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 * dupx )<< FRACBITS, scale); } static inline fixed_t BunchedCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul(std::max(1, (*cwp) - 1) << FRACBITS, scale); return 0; } static inline fixed_t MenuCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul(std::max(1, (*cwp) - 2) << FRACBITS, scale); return 0; } static inline fixed_t GamemodeCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul(std::max(1, (*cwp) - 2) << FRACBITS, scale); return 0; } static inline fixed_t FileCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul(std::max(1, (*cwp) - 3) << FRACBITS, scale); return 0; } static inline fixed_t LSTitleCharacterDim( fixed_t scale, fixed_t chw, INT32 hchw, INT32 dupx, fixed_t * cwp) { (void)chw; (void)hchw; (void)dupx; (*cwp) = FixedMul(std::max(1, (*cwp) - 4) << 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 *); UINT8 button_yofs; } 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; result->button_yofs = 0; const INT32 spacing = ( flags & V_SPACINGMASK ); switch (fontno) { default: case HU_FONT: case MENU_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; } break; case TINY_FONT: case TINYTIMER_FONT: result->spacew = 2; switch (spacing) { case V_MONOSPACE: result->spacew = 5; /* FALLTHRU */ case V_OLDSPACING: result->chw = 5; break; case V_6WIDTHSPACE: result->spacew = 3; break; } break; case MED_FONT: result->chw = 6; result->spacew = 6; break; case LT_FONT: result->spacew = 12; break; case CRED_FONT: result->spacew = 16; break; case KART_FONT: result->spacew = 3; switch (spacing) { case V_MONOSPACE: result->spacew = 12; /* FALLTHRU */ case V_OLDSPACING: result->chw = 12; break; case V_6WIDTHSPACE: result->spacew = 6; } break; case GM_FONT: result->spacew = 6; break; case FILE_FONT: result->spacew = 0; break; case LSHI_FONT: case LSLOW_FONT: result->spacew = 10; break; case OPPRF_FONT: result->spacew = 5; break; case PINGF_FONT: result->spacew = 3; break; case ROLNUM_FONT: result->spacew = 17; break; case RO4NUM_FONT: result->spacew = 9; break; } switch (fontno) { default: case HU_FONT: case MENU_FONT: case TINY_FONT: case TINYTIMER_FONT: case KART_FONT: case MED_FONT: result->lfh = 12; break; case LT_FONT: case CRED_FONT: case FILE_FONT: result->lfh = 12; break; case GM_FONT: result->lfh = 32; break; case LSHI_FONT: result->lfh = 56; break; case LSLOW_FONT: result->lfh = 38; break; case OPPRF_FONT: case PINGF_FONT: result->lfh = 10; break; case ROLNUM_FONT: result->lfh = 33; break; case RO4NUM_FONT: result->lfh = 15; break; } switch (fontno) { default: if (result->chw) result->dim_fn = CenteredCharacterDim; else result->dim_fn = VariableCharacterDim; break; case HU_FONT: if (result->chw) result->dim_fn = CenteredCharacterDim; else result->dim_fn = BunchedCharacterDim; break; case MENU_FONT: if (result->chw) result->dim_fn = CenteredCharacterDim; else result->dim_fn = MenuCharacterDim; break; case KART_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = BunchedCharacterDim; break; case TINY_FONT: case TINYTIMER_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = BunchedCharacterDim; break; case MED_FONT: result->dim_fn = FixedCharacterDim; break; case GM_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = GamemodeCharacterDim; break; case FILE_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = FileCharacterDim; break; case LSHI_FONT: case LSLOW_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = LSTitleCharacterDim; break; case OPPRF_FONT: case PINGF_FONT: case ROLNUM_FONT: case RO4NUM_FONT: if (result->chw) result->dim_fn = FixedCharacterDim; else result->dim_fn = BunchedCharacterDim; break; } switch (fontno) { case HU_FONT: result->button_yofs = 2; break; case MENU_FONT: result->button_yofs = 1; break; } } static UINT8 V_GetButtonCodeWidth(UINT8 c) { UINT8 x = 0; switch (c & 0x0F) { case 0x00: case 0x01: case 0x02: case 0x03: // arrows x = 12; break; case 0x07: case 0x08: case 0x09: // shoulders, start x = 14; break; case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: // faces x = 10; break; } return x; } void V_DrawStringScaled( fixed_t x, fixed_t y, fixed_t scale, fixed_t spacescale, fixed_t lfscale, INT32 flags, const UINT8 *colormap, int fontno, const char *s) { INT32 hchw;/* half-width for centering */ INT32 dupx; INT32 dupy; fixed_t right; fixed_t bot; font_t *font; boolean uppercase; boolean notcolored; 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_FORCEUPPERCASE) == V_FORCEUPPERCASE); flags &= ~(V_FLIP);/* These two (V_FORCEUPPERCASE) share a bit. */ dance = (flags & V_STRINGDANCE) != 0; nodanceoverride = !dance; dancecounter = 0; /* Some of these flags get overloaded in this function so don't pass them on. */ flags &= ~(V_PARAMMASK); 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 )) { dupx = vid.dupx; dupy = vid.dupy; hchw *= dupx; fontspec.chw *= dupx; fontspec.spacew *= dupx; fontspec.lfh *= dupy; right = vid.width; } else { dupx = 1; dupy = 1; right = ( vid.width / vid.dupx ); 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; } } 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; } if (( c & 0xB0 ) & 0x80) // button prompts { using srb2::Draw; struct BtConf { UINT8 x, y; Draw::Button type; }; auto bt_inst = [c]() -> std::optional { switch (c & 0x0F) { case 0x00: return {{0, 3, Draw::Button::up}}; case 0x01: return {{0, 3, Draw::Button::down}}; case 0x02: return {{0, 3, Draw::Button::right}}; case 0x03: return {{0, 3, Draw::Button::left}}; case 0x07: return {{0, 2, Draw::Button::r}}; case 0x08: return {{0, 2, Draw::Button::l}}; case 0x09: return {{0, 1, Draw::Button::start}}; case 0x0A: return {{2, 1, Draw::Button::a}}; case 0x0B: return {{2, 1, Draw::Button::b}}; case 0x0C: return {{2, 1, Draw::Button::c}}; case 0x0D: return {{2, 1, Draw::Button::x}}; case 0x0E: return {{2, 1, Draw::Button::y}}; case 0x0F: return {{2, 1, Draw::Button::z}}; default: return {}; } }(); if (bt_inst) { auto bt_translate_press = [c]() -> std::optional { switch (c & 0xB0) { default: case 0x90: return true; case 0xA0: return {}; case 0xB0: return false; } }; cw = V_GetButtonCodeWidth(c) * dupx; cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); Draw( FixedToFloat(cx + cxoff) - (bt_inst->x * dupx), FixedToFloat(cy + cyoff) - ((bt_inst->y + fontspec.button_yofs) * dupy)) .flags(flags) .small_button(bt_inst->type, bt_translate_press()); cx += cw; } break; } c -= font->start; if (V_CharacterValid(font, c) == true) { // Remove offsets from patch fixed_t patchxofs = SHORT (font->font[c]->leftoffset) * dupx * scale; cw = SHORT (font->font[c]->width) * dupx; cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); V_DrawFixedPatch(cx + cxoff + patchxofs, cy + cyoff, scale, 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 dupx; font_t *font; boolean uppercase; fixed_t cx; fixed_t right; fixed_t cw; int c; fixed_t fullwidth = 0; uppercase = ((flags & V_FORCEUPPERCASE) == V_FORCEUPPERCASE); flags &= ~(V_FLIP);/* These two (V_FORCEUPPERCASE) 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 )) { dupx = vid.dupx; hchw *= dupx; fontspec.chw *= dupx; fontspec.spacew *= dupx; fontspec.lfh *= vid.dupy; } else { dupx = 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 (( c & 0xB0 ) & 0x80) { cw = V_GetButtonCodeWidth(c) * dupx; cx += cw * scale; right = cx; break; } 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) * dupx; // 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, dupx, &cw); cx += cw; } else cx += fontspec.spacew; } fullwidth = std::max(right, std::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 dupx; font_t *font; boolean uppercase; fixed_t cx; fixed_t right; fixed_t cw; int c; uppercase = ((flags & V_FORCEUPPERCASE) == V_FORCEUPPERCASE); flags &= ~(V_FLIP);/* These two (V_FORCEUPPERCASE) 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 )) { dupx = vid.dupx; hchw *= dupx; fontspec.chw *= dupx; fontspec.spacew *= dupx; fontspec.lfh *= vid.dupy; } else { dupx = 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 = static_cast(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 (( c & 0xB0 ) & 0x80) // button prompts { cw = V_GetButtonCodeWidth(c) * dupx; cx += cw * scale; right = cx; } 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) * dupx; // 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, dupx, &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 = static_cast(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_DrawRightAlignedSmallString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_SmallStringWidth(string, option); V_DrawSmallString(x, y, option, string); } void V_DrawCenteredThinString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_ThinStringWidth(string, option)/2; V_DrawThinString(x, y, option, string); } void V_DrawRightAlignedThinString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_ThinStringWidth(string, option); V_DrawThinString(x, y, option, string); } void V_DrawCenteredStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) { x -= (V_ThinStringWidth(string, option) / 2) * FRACUNIT; V_DrawThinStringAtFixed(x, y, option, string); } void V_DrawRightAlignedStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) { x -= V_StringWidth(string, option) * FRACUNIT; V_DrawStringAtFixed(x, y, option, string); } void V_DrawCenteredThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) { x -= (V_StringWidth(string, option) / 2) * FRACUNIT; V_DrawStringAtFixed(x, y, option, string); } void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) { x -= V_ThinStringWidth(string, option) * FRACUNIT; V_DrawThinStringAtFixed(x, y, option, string); } // Draws a number using the PING font thingy. // TODO: Merge number drawing functions into one with "font name" selection. fixed_t V_DrawPingNum(fixed_t x, fixed_t y, INT32 flags, INT32 num, const UINT8 *colormap) { // this SHOULD always be 5 but I guess custom graphics exist. const fixed_t w = (fontv[PINGNUM_FONT].font[0]->width) * FRACUNIT; if (num < 0) { num = -num; } do // draw the number { x -= (w - FRACUNIT); // Oni wanted their outline to intersect. V_DrawFixedPatch(x, y, FRACUNIT, flags, fontv[PINGNUM_FONT].font[num % 10], colormap); num /= 10; } while (num > 0); return x; } void V_DrawCenteredTimerString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_TimerStringWidth(string, option)/2; V_DrawTimerString(x, y, option, string); } void V_DrawRightAlignedTimerString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_TimerStringWidth(string, option); V_DrawTimerString(x, y, option, string); } void V_DrawCenteredMenuString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_MenuStringWidth(string, option)/2; V_DrawMenuString(x, y, option, string); } void V_DrawRightAlignedMenuString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_MenuStringWidth(string, option); V_DrawMenuString(x, y, option, string); } void V_DrawCenteredGamemodeString(INT32 x, INT32 y, INT32 option, const UINT8 *colormap, const char *string) { x -= V_GamemodeStringWidth(string, option)/2; V_DrawGamemodeString(x, y, option, colormap, string); } void V_DrawRightAlignedGamemodeString(INT32 x, INT32 y, INT32 option, const UINT8 *colormap, const char *string) { x -= V_GamemodeStringWidth(string, option); V_DrawGamemodeString(x, y, option, colormap, string); } void V_DrawCenteredFileString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_FileStringWidth(string, option)/2; V_DrawFileString(x, y, option, string); } void V_DrawRightAlignedFileString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_FileStringWidth(string, option); V_DrawFileString(x, y, option, string); } void V_DrawCenteredLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_LSTitleHighStringWidth(string, option)/2; V_DrawLSTitleHighString(x, y, option, string); } void V_DrawRightAlignedLSTitleHighString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_LSTitleHighStringWidth(string, option); V_DrawLSTitleHighString(x, y, option, string); } void V_DrawCenteredLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_LSTitleLowStringWidth(string, option)/2; V_DrawLSTitleLowString(x, y, option, string); } void V_DrawRightAlignedLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string) { x -= V_LSTitleLowStringWidth(string, option); V_DrawLSTitleLowString(x, y, option, string); } // 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.dupx; 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.dupx; 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_DrawProfileNum(INT32 x, INT32 y, INT32 flags, UINT8 num) { UINT8 digits = 3; INT32 w = fontv[PROFNUM_FONT].font[0]->width; if (flags & V_NOSCALESTART) w *= vid.dupx; // draw the number do { x -= (w-1); V_DrawScaledPatch(x, y, flags, fontv[PROFNUM_FONT].font[num % 10]); num /= 10; } while (--digits); } // Find max height of the string // INT32 V_LevelNameHeight(const char *string) { INT32 c, w = 0; size_t i; for (i = 0; string[i]; 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; } // 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)) { INT32 i; lut->init = true; memcpy(lut->palette, palette, palsize); for (i = 0; i < 0xFFFF; 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; UINT8 *base = vid.buffer; const INT32 screensize = vid.rowbytes * vid.height; for (i = 0; i < NUMSCREENS; i++) screens[i] = NULL; // start address of NUMSCREENS * width*height vidbuffers if (base) { for (i = 0; i < NUMSCREENS; i++) screens[i] = base + i*screensize; } if (vid.direct) screens[0] = vid.direct; #ifdef DEBUG CONS_Debug(DBG_RENDER, "V_Init done:\n"); for (i = 0; i < NUMSCREENS; i++) CONS_Debug(DBG_RENDER, " 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 vid.dupx = vid.width / BASEVIDWIDTH; vid.dupy = vid.height / BASEVIDHEIGHT; vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy); vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT); vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT); #ifdef HWRENDER //if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl // 13/11/18: // The above is no longer necessary, since we want OpenGL to be just like software now // -- Monster Iestyn #endif vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy); vid.meddupx = (UINT8)(vid.dupx >> 1) + 1; vid.meddupy = (UINT8)(vid.dupy >> 1) + 1; #ifdef HWRENDER vid.fmeddupx = vid.meddupx*FRACUNIT; vid.fmeddupy = vid.meddupy*FRACUNIT; #endif vid.smalldupx = (UINT8)(vid.dupx / 3) + 1; vid.smalldupy = (UINT8)(vid.dupy / 3) + 1; #ifdef HWRENDER vid.fsmalldupx = vid.smalldupx*FRACUNIT; vid.fsmalldupy = vid.smalldupy*FRACUNIT; #endif } void VID_DisplaySoftwareScreen() { ZoneScoped; // TODO implement // upload framebuffer, bind pipeline, draw rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); rhi::Handle ctx = srb2::sys::main_graphics_context(); hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); // Misnomer; this just uploads the screen to the software indexed screen texture hw_state->software_screen_renderer->draw(*rhi, ctx); const int screens = std::clamp(r_splitscreen + 1, 1, MAXSPLITSCREENPLAYERS); hw_state->blit_postimg_screens->set_num_screens(screens); hw_state->blit_postimg_screens->set_target(static_cast(vid.width), static_cast(vid.height)); for (int i = 0; i < screens; i++) { glm::vec2 uv_offset {0.f, 0.f}; glm::vec2 uv_size {1.f, 1.f}; if (screens > 2) { uv_size = glm::vec2(.5f, .5f); switch (i) { case 0: uv_offset = glm::vec2(0.f, 0.f); break; case 1: uv_offset = glm::vec2(.5f, 0.f); break; case 2: uv_offset = glm::vec2(0.f, .5f); break; case 3: uv_offset = glm::vec2(.5f, .5f); break; } } else if (screens > 1) { uv_size = glm::vec2(1.f, .5f); if (i == 1) { uv_offset = glm::vec2(0.f, .5f); } } hw_state->blit_postimg_screens->set_screen( i, { hw_state->software_screen_renderer->screen(), true, uv_offset, uv_size, { postimgtype[i] == postimg_water, postimgtype[i] == postimg_heat, postimgtype[i] == postimg_flip, postimgtype[i] == postimg_mirror } } ); } // Post-process blit to the 'default' framebuffer hw_state->blit_postimg_screens->draw(*rhi, ctx); }