commit 1ac1de7728f7db6c6ffddf4b3d41f52e847c3130
Author: jens <jens.se@icloud.com>
AuthorDate: Fri Aug 21 23:33:47 2026 +0200
Commit: jens <jens.se@icloud.com>
CommitDate: Fri Aug 21 23:33:47 2026 +0200
Scale menu, intermission, title screen, and status bar for the larger buffer
SCREENWIDTH/SCREENHEIGHT were raised 2x for splitscreen (see i_video.h),
but the menu, intermission/score screen, title/credit screens, the
classic status bar, and the screen-size setting's view-size formula
were all still laid out in native ORIGWIDTH x ORIGHEIGHT (320x200)
terms, so they only occupied a small corner of the real screen instead
of filling it - the status bar in particular only had valid background
content in its native-width left half, leaving the rest uninitialized
and visibly off-center.
Adds V_DrawPatchScaled/V_DrawPatchDirectScaled/V_DrawPatchFlippedScaled
(v_video.c/h) - draws each patch pixel as a scale x scale block instead
of 1:1 - and applies it at the draw call in each affected file, scaling
only at the point of drawing so the existing vanilla layout math (menu
item positions, status bar widget offsets, intermission stat placement)
stays completely unchanged. Also fixes the screen-size setting's
scaledviewwidth/viewheight formula (r_main.c) to scale by the same
factor, restoring its full range of graduated steps instead of most of
it collapsing into barely-visible increments before jumping straight to
fullscreen.
The finale/cast-crawl screen has the same root issue and is still
native-resolution - noted in TODO.md as a follow-up, since its raw
pixel-scrolling code needs different handling than the patch-based
screens fixed here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
TODO.md | 5 +--
src/doom/d_main.c | 8 ++++-
src/doom/m_menu.c | 15 +++++++--
src/doom/r_main.c | 11 +++++--
src/doom/st_lib.c | 32 +++++++++++++------
src/doom/st_stuff.c | 29 ++++++++++-------
src/doom/st_stuff.h | 13 +++++---
src/doom/wi_stuff.c | 48 ++++++++++++++++++----------
src/v_video.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++--
src/v_video.h | 10 ++++++
10 files changed, 209 insertions(+), 52 deletions(-)
diff --git a/TODO.md b/TODO.md
index 840613b0..c2e45409 100644
--- a/TODO.md
+++ b/TODO.md
@@ -28,8 +28,9 @@ and unstructured wish list of features and improvements. The bug tracker
- Screensaver mode
* Infinite splitscreen fork:
- - Menu screen and intermission/score screen are too small in splitscreen -
- they're still laid out for a single full-width view.
+ - Finale/cast-crawl screen (f_finale.c) is still native-resolution and
+ too small, same root cause as the now-fixed menu/intermission/status
+ bar (see m_menu.c, wi_stuff.c, st_stuff.c/st_lib.c, i_video.h).
- Add a menu item for starting in splitscreen mode (currently only
reachable via -splitscreen on the command line).
- A player dying and respawning resets the whole game for everybody
diff --git a/src/doom/d_main.c b/src/doom/d_main.c
index 298067be..d23b9cfa 100644
--- a/src/doom/d_main.c
+++ b/src/doom/d_main.c
@@ -636,7 +636,13 @@ void D_PageTicker (void)
//
void D_PageDrawer (void)
{
- V_DrawPatch (0, 0, W_CacheLumpName(pagename, PU_CACHE));
+ // pagename lumps (TITLEPIC, CREDIT, ...) are native ORIGWIDTH x
+ // ORIGHEIGHT (320x200) full-screen art, same as the menu they can be
+ // shown underneath (see m_menu.c) - draw at the same scale so the
+ // two aren't visibly mismatched in size when the menu opens over the
+ // title screen.
+ V_DrawPatchScaled (0, 0, W_CacheLumpName(pagename, PU_CACHE),
+ SCREENWIDTH / ORIGWIDTH);
}
diff --git a/src/doom/m_menu.c b/src/doom/m_menu.c
index 71b47cbf..a600d585 100644
--- a/src/doom/m_menu.c
+++ b/src/doom/m_menu.c
@@ -61,6 +61,15 @@
#include "m_menu.h"
+// The entire menu is laid out in ORIGWIDTH x ORIGHEIGHT (320x200) terms,
+// same as vanilla - SCREENWIDTH/SCREENHEIGHT are larger now to give
+// splitscreen tiles more pixels (see i_video.h), which would otherwise
+// leave the menu occupying only a small corner of the real screen.
+// Redirect every V_DrawPatchDirect call in this file to the scaled
+// variant instead of touching each of the ~20 call sites individually -
+// safe because this file draws nothing else in real-screen-space terms.
+#define V_DrawPatchDirect(x, y, patch) \
+ V_DrawPatchDirectScaled((x), (y), (patch), (SCREENWIDTH / ORIGWIDTH))
//
// defaulted values
@@ -1325,7 +1334,7 @@ M_WriteText
}
w = SHORT (hu_font[c]->width);
- if (cx+w > SCREENWIDTH)
+ if (cx+w > ORIGWIDTH)
break;
V_DrawPatchDirect(cx, cy, hu_font[c]);
cx+=w;
@@ -1958,7 +1967,7 @@ void M_Drawer (void)
if (messageToPrint)
{
start = 0;
- y = SCREENHEIGHT/2 - M_StringHeight(messageString) / 2;
+ y = ORIGHEIGHT/2 - M_StringHeight(messageString) / 2;
while (messageString[start] != '\0')
{
boolean foundnewline = false;
@@ -1986,7 +1995,7 @@ void M_Drawer (void)
start += strlen(string);
}
- x = SCREENWIDTH/2 - M_StringWidth(string) / 2;
+ x = ORIGWIDTH/2 - M_StringWidth(string) / 2;
M_WriteText(x, y, string);
y += SHORT(hu_font[0]->height);
}
diff --git a/src/doom/r_main.c b/src/doom/r_main.c
index 6662c05d..cbbd9876 100644
--- a/src/doom/r_main.c
+++ b/src/doom/r_main.c
@@ -770,8 +770,15 @@ void R_ExecuteSetViewSize (void)
}
else
{
- scaledviewwidth = setblocks*32;
- viewheight = (setblocks*168/10)&~7;
+ // setblocks*32 and *168/10 are native ORIGWIDTH x ORIGHEIGHT
+ // (320x200) formulas - vanilla's only resolution, back when that
+ // was also SCREENWIDTH/SCREENHEIGHT (see i_video.h for why it no
+ // longer is). Scale the result (after vanilla's own rounding, so
+ // the step granularity this produces is unchanged) or most of the
+ // 3-10 range collapses into tiny, barely-visible increments on a
+ // larger buffer before jumping straight to fullscreen at 11.
+ scaledviewwidth = (setblocks*32) * (SCREENWIDTH / ORIGWIDTH);
+ viewheight = ((setblocks*168/10)&~7) * (SCREENHEIGHT / ORIGHEIGHT);
}
detailshift = setdetail;
diff --git a/src/doom/st_lib.c b/src/doom/st_lib.c
index 832bb66d..81e53278 100644
--- a/src/doom/st_lib.c
+++ b/src/doom/st_lib.c
@@ -35,6 +35,16 @@
#include "st_lib.h"
#include "r_local.h"
+// Widget positions (n->x, n->y, ...) are all in the native ORIGWIDTH x
+// ORIGHEIGHT terms ST_Y/ST_AMMOY/etc. (st_stuff.c) are defined in - see
+// st_stuff.h. Scale to real screen coordinates/sizes right at the
+// draw/erase calls below, same pattern as m_menu.c/wi_stuff.c.
+// st_backing_screen (see ST_refreshBackground) stores already-scaled
+// content at real (ST_WIDTH x ST_HEIGHT) * ST_SCALE size, so an erase
+// copy's SOURCE position within it is relative to the bar's own native
+// top (n->y - ST_Y) scaled, while its DEST position on the real screen
+// is the widget's absolute native position scaled directly.
+
//
// Hack display negative frags.
@@ -112,7 +122,9 @@ STlib_drawNum
if (n->y - ST_Y < 0)
I_Error("drawNum: n->y - ST_Y < 0");
- V_CopyRect(x, n->y - ST_Y, st_backing_screen, w*numdigits, h, x, n->y);
+ V_CopyRect(x * ST_SCALE, (n->y - ST_Y) * ST_SCALE, st_backing_screen,
+ w*numdigits * ST_SCALE, h * ST_SCALE,
+ x * ST_SCALE, n->y * ST_SCALE);
// if non-number, do not draw it
if (num == 1994)
@@ -122,19 +134,19 @@ STlib_drawNum
// in the special case of 0, you draw 0
if (!num)
- V_DrawPatch(x - w, n->y, n->p[ 0 ]);
+ V_DrawPatchScaled(x - w, n->y, n->p[ 0 ], ST_SCALE);
// draw the new number
while (num && numdigits--)
{
x -= w;
- V_DrawPatch(x, n->y, n->p[ num % 10 ]);
+ V_DrawPatchScaled(x, n->y, n->p[ num % 10 ], ST_SCALE);
num /= 10;
}
// draw a minus sign if necessary
if (neg && sttminus)
- V_DrawPatch(x - 8, n->y, sttminus);
+ V_DrawPatchScaled(x - 8, n->y, sttminus, ST_SCALE);
}
@@ -172,7 +184,7 @@ STlib_updatePercent
int refresh )
{
if (refresh && *per->n.on)
- V_DrawPatch(per->n.x, per->n.y, per->p);
+ V_DrawPatchScaled(per->n.x, per->n.y, per->p, ST_SCALE);
STlib_updateNum(&per->n, refresh);
}
@@ -222,9 +234,10 @@ STlib_updateMultIcon
if (y - ST_Y < 0)
I_Error("updateMultIcon: y - ST_Y < 0");
- V_CopyRect(x, y-ST_Y, st_backing_screen, w, h, x, y);
+ V_CopyRect(x * ST_SCALE, (y-ST_Y) * ST_SCALE, st_backing_screen,
+ w * ST_SCALE, h * ST_SCALE, x * ST_SCALE, y * ST_SCALE);
}
- V_DrawPatch(mi->x, mi->y, mi->p[*mi->inum]);
+ V_DrawPatchScaled(mi->x, mi->y, mi->p[*mi->inum], ST_SCALE);
mi->oldinum = *mi->inum;
}
}
@@ -272,9 +285,10 @@ STlib_updateBinIcon
I_Error("updateBinIcon: y - ST_Y < 0");
if (*bi->val)
- V_DrawPatch(bi->x, bi->y, bi->p);
+ V_DrawPatchScaled(bi->x, bi->y, bi->p, ST_SCALE);
else
- V_CopyRect(x, y-ST_Y, st_backing_screen, w, h, x, y);
+ V_CopyRect(x * ST_SCALE, (y-ST_Y) * ST_SCALE, st_backing_screen,
+ w * ST_SCALE, h * ST_SCALE, x * ST_SCALE, y * ST_SCALE);
bi->oldval = *bi->val;
}
diff --git a/src/doom/st_stuff.c b/src/doom/st_stuff.c
index b05256d4..ed5d1b15 100644
--- a/src/doom/st_stuff.c
+++ b/src/doom/st_stuff.c
@@ -80,13 +80,10 @@
#define ST_X2 104
#define ST_FX 143
-// Relative to ST_Y (which already correctly tracks SCREENHEIGHT), not a
-// hardcoded absolute row: these were all originally calibrated assuming
-// SCREENHEIGHT==200 (so ST_Y==168), silently baking that in as e.g.
-// "169" instead of "ST_Y+1". At a taller SCREENHEIGHT (see i_video.h)
-// ST_Y moves down but these didn't, eventually landing above ST_Y and
-// hitting STlib_drawNum's "n->y - ST_Y < 0" I_Error. Preserves the exact
-// original vanilla appearance at 320x200 (168+offset == the old literal).
+// Relative to ST_Y, not a hardcoded absolute row ("169" instead of
+// "ST_Y+1") - both are native ORIGWIDTH x ORIGHEIGHT coordinates (see
+// st_stuff.h), scaled to the real screen at the point of drawing
+// (st_lib.c), so this stays exactly the vanilla layout math either way.
#define ST_FY (ST_Y + 1)
// Number of status faces.
@@ -354,20 +351,28 @@ void ST_refreshBackground(void)
if (st_statusbaron)
{
+ // st_backing_screen is allocated at real (ST_WIDTH x ST_HEIGHT)
+ // * ST_SCALE size (see ST_Init) and holds already-scaled
+ // content, so drawing into it while redirected here still needs
+ // the scaled variants - V_UseBuffer only swaps which buffer
+ // dest_screen points to, not its width, and both it and the
+ // real screen share the same SCREENWIDTH row stride.
V_UseBuffer(st_backing_screen);
- V_DrawPatch(ST_X, 0, sbar);
+ V_DrawPatchScaled(ST_X, 0, sbar, ST_SCALE);
// draw right side of bar if needed (Doom 1.0)
if (sbarr)
- V_DrawPatch(ST_ARMSBGX, 0, sbarr);
+ V_DrawPatchScaled(ST_ARMSBGX, 0, sbarr, ST_SCALE);
if (netgame)
- V_DrawPatch(ST_FX, 0, faceback);
+ V_DrawPatchScaled(ST_FX, 0, faceback, ST_SCALE);
V_RestoreBuffer();
- V_CopyRect(ST_X, 0, st_backing_screen, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y);
+ V_CopyRect(ST_X * ST_SCALE, 0, st_backing_screen,
+ ST_WIDTH * ST_SCALE, ST_HEIGHT * ST_SCALE,
+ ST_X * ST_SCALE, ST_Y * ST_SCALE);
}
}
@@ -1371,7 +1376,7 @@ void ST_Stop (void)
void ST_Init (void)
{
ST_loadData();
- st_backing_screen = (pixel_t *) Z_Malloc(ST_WIDTH * ST_HEIGHT * sizeof(*st_backing_screen), PU_STATIC, 0);
+ st_backing_screen = (pixel_t *) Z_Malloc(ST_WIDTH * ST_SCALE * ST_HEIGHT * ST_SCALE * sizeof(*st_backing_screen), PU_STATIC, 0);
}
//
diff --git a/src/doom/st_stuff.h b/src/doom/st_stuff.h
index efcc83d3..a6a111e1 100644
--- a/src/doom/st_stuff.h
+++ b/src/doom/st_stuff.h
@@ -26,11 +26,16 @@
#include "d_player.h"
#include "m_cheat.h"
-// Size of statusbar.
-// Now sensitive for scaling.
+// Size of statusbar, and its position/widget layout macros (ST_AMMOY,
+// ST_ARMSX, etc. in st_stuff.c) - all in native ORIGWIDTH x ORIGHEIGHT
+// (320x200) terms, same as vanilla and same as m_menu.c/wi_stuff.c (see
+// i_video.h for why SCREENWIDTH/SCREENHEIGHT no longer match that).
+// st_lib.c/st_stuff.c scale by ST_SCALE at the point of drawing instead
+// of here, so this stays exactly the vanilla layout math.
#define ST_HEIGHT 32
-#define ST_WIDTH SCREENWIDTH
-#define ST_Y (SCREENHEIGHT - ST_HEIGHT)
+#define ST_WIDTH ORIGWIDTH
+#define ST_Y (ORIGHEIGHT - ST_HEIGHT)
+#define ST_SCALE (SCREENWIDTH / ORIGWIDTH)
//
diff --git a/src/doom/wi_stuff.c b/src/doom/wi_stuff.c
index 8d2ac54d..6ed63188 100644
--- a/src/doom/wi_stuff.c
+++ b/src/doom/wi_stuff.c
@@ -41,10 +41,24 @@
#include "sounds.h"
// Needs access to LFB.
+#include "i_video.h"
#include "v_video.h"
#include "wi_stuff.h"
+// The intermission screen is laid out in ORIGWIDTH x ORIGHEIGHT (320x200)
+// terms, same as vanilla (see i_video.h for why SCREENWIDTH/SCREENHEIGHT
+// no longer match that). Redirect every V_DrawPatch call in this file to
+// the scaled variant instead of touching each call site individually.
+// This file's own layout math also uses SCREENWIDTH/SCREENHEIGHT in
+// several places standing in for ORIGWIDTH/ORIGHEIGHT (centering,
+// right-aligning, the SP_TIMEY macro, WI_drawOnLnode's bounds check) -
+// those are fixed at each use below instead of here, since they're
+// values passed into V_DrawPatch rather than V_DrawPatch calls
+// themselves.
+#define V_DrawPatch(x, y, patch) \
+ V_DrawPatchScaled((x), (y), (patch), (SCREENWIDTH / ORIGWIDTH))
+
//
// Data needed to add patches to full screen intermission pics.
// Patches are statistics messages, and animations.
@@ -80,7 +94,7 @@
#define SP_STATSY 50
#define SP_TIMEX 16
-#define SP_TIMEY (SCREENHEIGHT-32)
+#define SP_TIMEY (ORIGHEIGHT-32)
// NET GAME STUFF
@@ -428,19 +442,19 @@ void WI_drawLF(void)
if (gamemode != commercial || wbs->last < NUMCMAPS)
{
- // draw <LevelName>
- V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->last]->width))/2,
+ // draw <LevelName>
+ V_DrawPatch((ORIGWIDTH - SHORT(lnames[wbs->last]->width))/2,
y, lnames[wbs->last]);
// draw "Finished!"
y += (5*SHORT(lnames[wbs->last]->height))/4;
- V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished);
+ V_DrawPatch((ORIGWIDTH - SHORT(finished->width)) / 2, y, finished);
}
else if (wbs->last == NUMCMAPS)
{
// MAP33 - draw "Finished!" only
- V_DrawPatch((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished);
+ V_DrawPatch((ORIGWIDTH - SHORT(finished->width)) / 2, y, finished);
}
else if (wbs->last > NUMCMAPS)
{
@@ -449,7 +463,7 @@ void WI_drawLF(void)
// bits of memory at this point, but let's try to be accurate
// anyway. This deliberately triggers a V_DrawPatch error.
- patch_t tmp = { SCREENWIDTH, SCREENHEIGHT, 1, 1,
+ patch_t tmp = { ORIGWIDTH, ORIGHEIGHT, 1, 1,
{ 0, 0, 0, 0, 0, 0, 0, 0 } };
V_DrawPatch(0, y, &tmp);
@@ -464,15 +478,15 @@ void WI_drawEL(void)
int y = WI_TITLEY;
// draw "Entering"
- V_DrawPatch((SCREENWIDTH - SHORT(entering->width))/2,
+ V_DrawPatch((ORIGWIDTH - SHORT(entering->width))/2,
y,
entering);
// draw level
y += (5*SHORT(lnames[wbs->next]->height))/4;
- V_DrawPatch((SCREENWIDTH - SHORT(lnames[wbs->next]->width))/2,
- y,
+ V_DrawPatch((ORIGWIDTH - SHORT(lnames[wbs->next]->width))/2,
+ y,
lnames[wbs->next]);
}
@@ -499,9 +513,9 @@ WI_drawOnLnode
bottom = top + SHORT(c[i]->height);
if (left >= 0
- && right < SCREENWIDTH
+ && right < ORIGWIDTH
&& top >= 0
- && bottom < SCREENHEIGHT)
+ && bottom < ORIGHEIGHT)
{
fits = true;
}
@@ -1472,21 +1486,21 @@ void WI_drawStats(void)
WI_drawLF();
V_DrawPatch(SP_STATSX, SP_STATSY, kills);
- WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY, cnt_kills[0]);
+ WI_drawPercent(ORIGWIDTH - SP_STATSX, SP_STATSY, cnt_kills[0]);
V_DrawPatch(SP_STATSX, SP_STATSY+lh, items);
- WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY+lh, cnt_items[0]);
+ WI_drawPercent(ORIGWIDTH - SP_STATSX, SP_STATSY+lh, cnt_items[0]);
V_DrawPatch(SP_STATSX, SP_STATSY+2*lh, sp_secret);
- WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]);
+ WI_drawPercent(ORIGWIDTH - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]);
V_DrawPatch(SP_TIMEX, SP_TIMEY, timepatch);
- WI_drawTime(SCREENWIDTH/2 - SP_TIMEX, SP_TIMEY, cnt_time);
+ WI_drawTime(ORIGWIDTH/2 - SP_TIMEX, SP_TIMEY, cnt_time);
if (wbs->epsd < 3)
{
- V_DrawPatch(SCREENWIDTH/2 + SP_TIMEX, SP_TIMEY, par);
- WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par);
+ V_DrawPatch(ORIGWIDTH/2 + SP_TIMEX, SP_TIMEY, par);
+ WI_drawTime(ORIGWIDTH - SP_TIMEX, SP_TIMEY, cnt_par);
}
}
diff --git a/src/v_video.c b/src/v_video.c
index c35f259f..fbf49852 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -269,8 +269,94 @@ void V_DrawPatchFlipped(int x, int y, patch_t *patch)
void V_DrawPatchDirect(int x, int y, patch_t *patch)
{
- V_DrawPatch(x, y, patch);
-}
+ V_DrawPatch(x, y, patch);
+}
+
+//
+// V_DrawPatchScaledCore
+// Shared core of V_DrawPatchScaled/V_DrawPatchDirectScaled/
+// V_DrawPatchFlippedScaled (see v_video.h) - draws each source pixel as
+// a scale x scale block instead of 1:1, so UI code still laid out in
+// ORIGWIDTH x ORIGHEIGHT terms (menu, intermission, ...) can fill a
+// larger SCREENWIDTH x SCREENHEIGHT framebuffer without every call site
+// having to redo its layout math.
+//
+static void V_DrawPatchScaledCore(int x, int y, patch_t *patch, int scale, boolean flip)
+{
+ int count;
+ int col;
+ int srccol;
+ int w;
+ int sy;
+ int dx, dy;
+ int px, py;
+ column_t *column;
+ byte *source;
+ pixel_t *dest;
+
+ y -= SHORT(patch->topoffset);
+ x -= SHORT(patch->leftoffset);
+
+ if (patchclip_callback)
+ {
+ if (!patchclip_callback(patch, x, y))
+ return;
+ }
+
+ w = SHORT(patch->width);
+
+ // Not guarded behind #ifdef RANGECHECK like V_DrawPatch's sibling
+ // functions - those preserve a vanilla behavior (I_Error on bad
+ // input), but there's no vanilla precedent for scaled UI drawing to
+ // match, so just skip rather than risk writing past dest_screen.
+ if (x < 0 || y < 0
+ || (x + w) * scale > SCREENWIDTH
+ || (y + SHORT(patch->height)) * scale > SCREENHEIGHT)
+ {
+ return;
+ }
+
+ V_MarkRect(x * scale, y * scale, w * scale, SHORT(patch->height) * scale);
+
+ for (col = 0 ; col < w ; col++, x++)
+ {
+ srccol = flip ? (w - 1 - col) : col;
+ column = (column_t *)((byte *)patch + LONG(patch->columnofs[srccol]));
+
+ while (column->topdelta != 0xff)
+ {
+ source = (byte *)column + 3;
+ count = column->length;
+
+ for (sy = 0 ; sy < count ; sy++)
+ {
+ py = (y + column->topdelta + sy) * scale;
+ px = x * scale;
+ dest = dest_screen + py * SCREENWIDTH + px;
+
+ for (dy = 0 ; dy < scale ; dy++, dest += SCREENWIDTH)
+ for (dx = 0 ; dx < scale ; dx++)
+ dest[dx] = source[sy];
+ }
+ column = (column_t *)((byte *)column + column->length + 4);
+ }
+ }
+}
+
+void V_DrawPatchScaled(int x, int y, patch_t *patch, int scale)
+{
+ V_DrawPatchScaledCore(x, y, patch, scale, false);
+}
+
+void V_DrawPatchDirectScaled(int x, int y, patch_t *patch, int scale)
+{
+ V_DrawPatchScaledCore(x, y, patch, scale, false);
+}
+
+void V_DrawPatchFlippedScaled(int x, int y, patch_t *patch, int scale)
+{
+ V_DrawPatchScaledCore(x, y, patch, scale, true);
+}
//
// V_DrawTLPatch
diff --git a/src/v_video.h b/src/v_video.h
index f9c18f9a..b0df085f 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -62,6 +62,16 @@ void V_DrawShadowedPatch(int x, int y, patch_t *patch);
void V_DrawXlaPatch(int x, int y, patch_t * patch); // villsa [STRIFE]
void V_DrawPatchDirect(int x, int y, patch_t *patch);
+// Like V_DrawPatch/V_DrawPatchDirect/V_DrawPatchFlipped, but draws each
+// source pixel as a scale x scale block: for UI screens (menu,
+// intermission, ...) whose layout math is still authored in ORIGWIDTH x
+// ORIGHEIGHT terms (see i_video.h) but need to fill a larger SCREENWIDTH
+// x SCREENHEIGHT framebuffer. x/y are in the same unscaled coordinate
+// space the patch's own dimensions are.
+void V_DrawPatchScaled(int x, int y, patch_t *patch, int scale);
+void V_DrawPatchDirectScaled(int x, int y, patch_t *patch, int scale);
+void V_DrawPatchFlippedScaled(int x, int y, patch_t *patch, int scale);
+
// Draw a linear block of pixels into the view buffer.
void V_DrawBlock(int x, int y, int width, int height, pixel_t *src);