The Wipes Upgrade

* All those fugly main loop copies are GONE! Replaced by one powerful loop
* Chat, menus and console can now be used during wipes
* Wipes are now interpolation-enabled
* Screenshotting now works consistently
This commit is contained in:
GenericHeroGuy 2025-03-28 23:08:57 +01:00
parent 0a893b5395
commit b34a7cb66b
8 changed files with 199 additions and 176 deletions

View file

@ -132,6 +132,8 @@ boolean devparm = false; // started game with -devparm
boolean singletics = false; // timedemo
boolean lastdraw = false;
static INT32 lastwipetic = 0;
INT32 postimgparam[MAXSPLITSCREENPLAYERS];
// These variables are in effect
@ -734,6 +736,178 @@ static bool D_Display(void)
return ranwipe;
}
static void D_WipeTick(boolean menu)
{
I_OsPolling();
D_ProcessEvents();
// Update the network so we don't cause timeouts
NetKeepAlive();
HU_Ticker();
if (menu)
{
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Ticker();
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
}
CON_Ticker();
}
static void D_WipeDraw(boolean menu)
{
if (rendermode == render_none)
return;
I_UpdateNoBlit();
HU_Erase();
HU_Drawer();
if (menu)
{
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer();
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
}
CON_Drawer();
I_FinishUpdate(); // page flip or blit buffer
}
static double D_EndFrame(precise_t enterprecise, int *frameskip)
{
// Fully completed frame made.
precise_t finishprecise = I_GetPreciseTime();
// Use the time before sleep for frameskip calculations:
// post-sleep time is literally being intentionally wasted
double deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
double deltatics = deltasecs * NEWTICRATE;
// If time spent this game loop exceeds a single tic,
// it's probably because of rendering.
//
// Skip rendering the next frame, up to a limit of 3
// frames before a frame is rendered no matter what.
if (frameskip)
{
if (*frameskip < 3 && deltatics > 1.0)
*frameskip += 1;
else
*frameskip = 0;
}
if (!singletics)
{
precise_t elapsed = finishprecise - enterprecise;
// capbudget is the minimum precise_t duration of a single loop iteration
precise_t capbudget = round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision());
// in the case of "match refresh rate" + vsync, don't sleep at all
const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0;
if (elapsed > 0 && capbudget > elapsed && !vsync_with_match_refresh)
{
I_SleepDuration(capbudget - (finishprecise - enterprecise));
}
}
// Capture the time once more to get the real delta time.
finishprecise = I_GetPreciseTime();
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
return deltasecs * NEWTICRATE;
}
static INT32 endtimes[] = {
[WIPELOOP_RUNWIPE] = UINT8_MAX,
[WIPELOOP_TITLECARD] = PRELEVELTIME*NEWTICRATERATIO,
[WIPELOOP_ENCORE] = 3*TICRATE/2,
[WIPELOOP_TITLEBLACK] = NEWTICRATE, // Shortened the quit time, used to be 2 seconds
};
// one single function for all the extra main loops in this god-forsaken codebase
// should make it easier to fold these back into D_SRB2Loop at some point...
void D_WipeLoop(wipelooptype_t type, UINT8 wipetype, boolean drawMenu)
{
tic_t nowtime, endtime;
UINT8 wipeframe = 0;
fademask_t *fmask;
double delta = 0.0;
nowtime = lastwipetic = I_GetTime();
endtime = nowtime + endtimes[type];
if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
drawMenu = true;
// lastwipetic should either be 0 or the tic we last wiped
// on for fade-to-black
while (nowtime < endtime)
{
precise_t enterprecise = I_GetPreciseTime();
I_UpdateTime(cv_timescale.value);
nowtime = I_GetTime();
// wait loop
if (nowtime - lastwipetic)
{
renderisnewtic = true;
wipeframe++;
D_WipeTick(drawMenu);
if (type == WIPELOOP_TITLECARD)
ST_runTitleCard();
}
else
renderisnewtic = false;
// draw loop
rendertimefrac = g_time.timefrac;
renderdeltatics = FLOAT_TO_FIXED(delta);
#ifndef NOWIPE
if (type == WIPELOOP_RUNWIPE)
{
// get fademask first so we can tell if it exists or not
fmask = F_GetFadeMask(wipetype, wipeframe);
if (!fmask)
break;
#ifdef HWRENDER
if (rendermode == render_opengl)
HWR_DoWipe(wipetype, wipeframe); // send in the wipe type and wipeframe because we need to cache the graphic
else
#endif
if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers
F_DoWipe(fmask);
}
#endif
if (type == WIPELOOP_TITLECARD)
ST_preLevelTitleCardDrawer();
D_WipeDraw(drawMenu);
// Only take screenshots after drawing.
if (moviemode)
M_SaveFrame();
if (takescreenshot)
M_DoScreenShot();
delta = D_EndFrame(enterprecise, NULL);
lastwipetic = nowtime;
}
}
// =========================================================================
// D_SRB2Loop
// =========================================================================
@ -744,7 +918,6 @@ void D_SRB2Loop(void)
{
tic_t entertic = 0, oldentertics = 0, realtics = 0, rendertimeout = INFTICS;
double deltatics = 0.0;
double deltasecs = 0.0;
boolean interp = false;
boolean doDisplay = false;
@ -797,20 +970,11 @@ void D_SRB2Loop(void)
for (;;)
{
// capbudget is the minimum precise_t duration of a single loop iteration
precise_t capbudget;
precise_t enterprecise = I_GetPreciseTime();
precise_t finishprecise = enterprecise;
g_dc = {};
Z_Frame_Reset();
{
// Casting the return value of a function is bad practice (apparently)
double budget = round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision());
capbudget = (precise_t) budget;
}
bool ranwipe = false;
I_UpdateTime(cv_timescale.value);
@ -965,47 +1129,11 @@ void D_SRB2Loop(void)
}
#endif
// Fully completed frame made.
finishprecise = I_GetPreciseTime();
// Use the time before sleep for frameskip calculations:
// post-sleep time is literally being intentionally wasted
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
deltatics = deltasecs * NEWTICRATE;
// If time spent this game loop exceeds a single tic,
// it's probably because of rendering.
//
// Skip rendering the next frame, up to a limit of 3
// frames before a frame is rendered no matter what.
//
// Wipes run an inner loop and artificially increase
// the measured time.
if (!ranwipe && frameskip < 3 && deltatics > 1.0)
{
frameskip++;
}
else
{
if (ranwipe)
frameskip = 0;
}
if (!singletics)
{
INT64 elapsed = (INT64)(finishprecise - enterprecise);
// in the case of "match refresh rate" + vsync, don't sleep at all
const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0;
if (elapsed > 0 && (INT64)capbudget > elapsed && !vsync_with_match_refresh)
{
I_SleepDuration(capbudget - (finishprecise - enterprecise));
}
}
// Capture the time once more to get the real delta time.
finishprecise = I_GetPreciseTime();
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
deltatics = deltasecs * NEWTICRATE;
deltatics = D_EndFrame(enterprecise, !ranwipe ? &frameskip : NULL);
}
}

View file

@ -61,6 +61,16 @@ extern char srb2path[256]; //Alam: SRB2's Home
// the infinite loop of D_SRB2Loop() called from win_main for windows version
void D_SRB2Loop(void) FUNCNORETURN;
typedef enum
{
WIPELOOP_RUNWIPE,
WIPELOOP_TITLECARD,
WIPELOOP_ENCORE,
WIPELOOP_TITLEBLACK,
} wipelooptype_t;
void D_WipeLoop(wipelooptype_t type, UINT8 wipetype, boolean drawMenu);
//
// D_SRB2Main()
// Not a globally visible function, just included for source reference,

View file

@ -431,34 +431,7 @@ void F_IntroTicker(void)
}
// Stay on black for a bit. =)
{
tic_t nowtime, quittime, lasttime;
nowtime = lasttime = I_GetTime();
quittime = nowtime + NEWTICRATE; // Shortened the quit time, used to be 2 seconds
while (quittime > nowtime)
{
while (!((nowtime = I_GetTime()) - lasttime))
{
I_Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
}
lasttime = nowtime;
I_OsPolling();
I_UpdateNoBlit();
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer(); // menu is drawn even on top of wipes
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
if (moviemode) // make sure we save frames for the white hold too
M_SaveFrame();
}
}
D_WipeLoop(WIPELOOP_TITLEBLACK, 0, false);
D_StartTitle();
return;

View file

@ -145,12 +145,12 @@ void F_MenuPresTicker(boolean run);
extern boolean WipeInAction;
extern boolean WipeStageTitle;
extern INT32 lastwipetic;
// Don't know where else to place this constant
// But this file seems appropriate
#define PRELEVELTIME TICRATE // frames in tics
fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum);
void F_DoWipe(fademask_t *fademask);
void F_WipeStartScreen(void);
void F_WipeEndScreen(void);
void F_RunWipe(UINT8 wipetype, boolean drawMenu);

View file

@ -43,12 +43,12 @@
#define NOWIPE // do not enable wipe image post processing for ARM, SH and MIPS CPUs
#endif
typedef struct fademask_s {
struct fademask_t {
UINT8* mask;
UINT16 width, height;
size_t size;
fixed_t xscale, yscale;
} fademask_t;
};
UINT8 wipedefs[NUMWIPEDEFS] = {
99, // wipe_credits_intermediate (0)
@ -85,7 +85,6 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
boolean WipeInAction = false;
boolean WipeStageTitle = false;
INT32 lastwipetic = 0;
#ifndef NOWIPE
@ -101,7 +100,7 @@ static fixed_t paldiv;
* \param lump Lump name to get data from
* \return fademask_t for lump
*/
static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
static char lumpname[9] = "FADEmmss";
static fademask_t fm = {NULL,0,0,0,0,0};
lumpnum_t lumpnum;
@ -186,7 +185,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
*
* \param fademask pixels to change
*/
static void F_DoWipe(fademask_t *fademask)
void F_DoWipe(fademask_t *fademask)
{
// Software mask wipe -- optimized; though it might not look like it!
// Okay, to save you wondering *how* this is more optimized than the simpler
@ -360,62 +359,14 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
(void)wipetype;
(void)drawMenu;
#else
tic_t nowtime;
UINT8 wipeframe = 0;
fademask_t *fmask;
paldiv = FixedDiv(257<<FRACBITS, 11<<FRACBITS);
// Init the wipe
WipeInAction = true;
wipe_scr = screens[0];
// lastwipetic should either be 0 or the tic we last wiped
// on for fade-to-black
for (;;)
{
// get fademask first so we can tell if it exists or not
fmask = F_GetFadeMask(wipetype, wipeframe++);
if (!fmask)
break;
D_WipeLoop(WIPELOOP_RUNWIPE, wipetype, drawMenu);
// wait loop
while (!((nowtime = I_GetTime()) - lastwipetic))
{
I_Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
}
lastwipetic = nowtime;
#ifdef HWRENDER
if (rendermode == render_opengl)
HWR_DoWipe(wipetype, wipeframe-1); // send in the wipe type and wipeframe because we need to cache the graphic
else
#endif
if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers
F_DoWipe(fmask);
I_OsPolling();
I_UpdateNoBlit();
if (drawMenu && rendermode != render_none)
{
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer(); // menu is drawn even on top of wipes
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
}
I_FinishUpdate(); // page flip or blit buffer
if (moviemode)
M_SaveFrame();
NetKeepAlive(); // Update the network so we don't cause timeouts
}
WipeInAction = false;
#endif
}

View file

@ -1507,30 +1507,7 @@ void G_StartTitleCard(void)
void G_PreLevelTitleCard(void)
{
#ifndef NOWIPE
tic_t strtime = I_GetTime();
tic_t endtime = strtime + (PRELEVELTIME*NEWTICRATERATIO);
tic_t nowtime = strtime;
tic_t lasttime = strtime;
while (nowtime < endtime)
{
// draw loop
ST_runTitleCard();
ST_preLevelTitleCardDrawer();
I_FinishUpdate(); // page flip or blit buffer
NetKeepAlive(); // Prevent timeouts
if (moviemode)
M_SaveFrame();
if (takescreenshot) // Only take screenshots after drawing.
M_DoScreenShot();
while (!((nowtime = I_GetTime()) - lasttime))
{
I_Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
}
lasttime = nowtime;
}
D_WipeLoop(WIPELOOP_TITLECARD, 0, false);
#endif
}

View file

@ -8637,8 +8637,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
// This is handled BEFORE sounds are stopped.
else if (encoremode && !prevencoremode && !demo.rewinding)
{
tic_t locstarttime, endtime, nowtime;
if (rendermode != render_none)
{
S_StopMusic(); // er, about that...
@ -8663,25 +8661,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
F_RunWipe(wipedefs[wipe_level_final], false);
}
locstarttime = nowtime = lastwipetic;
endtime = locstarttime + (3*TICRATE)/2;
// Hold on white for extra effect.
while (nowtime < endtime)
{
// wait loop
while (!((nowtime = I_GetTime()) - lastwipetic))
{
I_Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
}
lastwipetic = nowtime;
if (moviemode) // make sure we save frames for the white hold too
M_SaveFrame();
// Keep the network alive
NetKeepAlive();
}
D_WipeLoop(WIPELOOP_ENCORE, 0, false);
ranspecialwipe = 1;
}

View file

@ -121,6 +121,9 @@ TYPEDEF (cupheader_t);
TYPEDEF (exitcondition_t);
TYPEDEF (mapheader_lighting_t);
// f_finale.h
TYPEDEF (fademask_t);
// font.h
TYPEDEF (font_t);