From d755d6edb9d2ff2e911504732de2bef5fd6f815d Mon Sep 17 00:00:00 2001 From: Lat Date: Tue, 21 Feb 2023 16:16:34 +0100 Subject: [PATCH 01/60] Titlecard CEcho --- src/acs/call-funcs.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/acs/call-funcs.hpp | 2 ++ src/acs/environment.cpp | 7 ++++++- src/g_game.c | 2 ++ src/hu_stuff.c | 41 +++++++++++++++++++++++++++++++++++++++++ src/hu_stuff.h | 4 ++++ src/lua_baselib.c | 14 +++++++++++++- src/p_setup.c | 1 + 8 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 3e7ca5040..780455f07 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -565,6 +565,25 @@ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo return false; } +/*-------------------------------------------------- + bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + One of the ACS wrappers for Titlecard CEcho. This + version only prints if the activator is a + display player. +--------------------------------------------------*/ +bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + if (ACS_ActivatorIsLocal(thread) == true) + HU_DoTitlecardCEcho(thread->printBuf.data()); + + thread->printBuf.drop(); + return false; +} + /*-------------------------------------------------- bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -935,6 +954,23 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM return false; } +/*-------------------------------------------------- + bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + One of the ACS wrappers for Titlecard CEcho. This + version prints for all players. +--------------------------------------------------*/ +bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + HU_DoTitlecardCEcho(thread->printBuf.data()); + + thread->printBuf.drop(); + return false; +} + /*-------------------------------------------------- bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 4bbe3eaec..9e634c0d9 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -55,6 +55,7 @@ bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSV bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GameSpeed(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); @@ -65,6 +66,7 @@ bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACS bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index ad593e74b..28078967d 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -100,7 +100,12 @@ Environment::Environment() addCodeDataACS0(120, {"", 0, addCallFunc(CallFunc_PlayerRings)}); addCodeDataACS0(122, {"", 0, addCallFunc(CallFunc_PlayerScore)}); - + + // Lat: Titlecard CEcho. I'm not sure if I should be putting it here. + // @TODO: Confirm this is fine? + addCodeDataACS0( 123, {"", 0, addCallFunc(CallFunc_EndPrintTitlecard)}); + addCodeDataACS0( 124, {"", 0, addCallFunc(CallFunc_EndPrintBoldTitlecard)}); + // 136 to 137: Implemented by ACSVM // 157: Implemented by ACSVM diff --git a/src/g_game.c b/src/g_game.c index ecf088eaf..4ff66a88c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3145,6 +3145,7 @@ void G_ExitLevel(void) // Remove CEcho text on round end. HU_ClearCEcho(); + HU_ClearTitlecardCEcho(); // Don't save demos immediately here! Let standings write first } @@ -4124,6 +4125,7 @@ void G_AfterIntermission(void) gamecomplete = 1; HU_ClearCEcho(); + HU_ClearTitlecardCEcho(); if (demo.playback) { diff --git a/src/hu_stuff.c b/src/hu_stuff.c index a457fe774..8521049a8 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -161,6 +161,11 @@ static tic_t cechotimer = 0; static tic_t cechoduration = 5*TICRATE; static INT32 cechoflags = 0; +static char tcechotext[48]; // the text is wide so only 48 chars should do. +static tic_t tcechotimer = 0; // goes up by 1 each frame this is active +static tic_t tcechoduration = 0; // Set automatically + + static tic_t resynch_ticker = 0; static huddrawlist_h luahuddrawlist_scores; @@ -1035,6 +1040,13 @@ void HU_Ticker(void) if (cechotimer) cechotimer--; + + if (tcechotimer) + { + tcechotimer++; + if (tcechotimer > tcechoduration) + tcechotimer = 0; + } if (gamestate != GS_LEVEL) { @@ -2000,6 +2012,15 @@ static void HU_DrawCEcho(void) } } +static void HU_DrawTitlecardCEcho(void) +{ + if (tcechotimer) + { + INT32 w = V_TitleCardStringWidth(tcechotext); + V_DrawTitleCardString(160 -w/2, 90, tcechotext, 0, false, tcechotimer, TICRATE*4); + } +} + // // demo info stuff // @@ -2145,6 +2166,9 @@ drawontop: if (cechotimer) HU_DrawCEcho(); + + if (tcechotimer) + HU_DrawTitlecardCEcho(); } //====================================================================== @@ -2598,3 +2622,20 @@ void HU_DoCEcho(const char *msg) cechotext[sizeof(cechotext) - 1] = '\0'; cechotimer = cechoduration; } + +// Simply set the timer to 0 to clear it. +// No need to bother clearing the buffer or anything. +void HU_ClearTitlecardCEcho(void) +{ + tcechotimer = 0; +} + +// Similar but for titlecard CEcho and also way less convoluted because I have no clue whatever the fuck they were trying above. +void HU_DoTitlecardCEcho(const char *msg) +{ + I_OutputMsg("%s\n", msg); // print to log + + strncpy(tcechotext, msg, sizeof(tcechotext)); + tcechotimer = 1; + tcechoduration = TICRATE*6 + strlen(tcechotext); +} \ No newline at end of file diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 3e8233020..144e0e557 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -153,6 +153,10 @@ void HU_SetCEchoDuration(INT32 seconds); void HU_SetCEchoFlags(INT32 flags); void HU_DoCEcho(const char *msg); +// Titlecard CECHO shite +void HU_DoTitlecardCEcho(const char *msg); +void HU_ClearTitlecardCEcho(void); + // Demo playback info extern UINT32 hu_demotime; extern UINT32 hu_demolap; diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 4dead2615..654dc71d0 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -35,6 +35,7 @@ #include "k_menu.h" // Player Setup menu color stuff #include "p_spec.h" // P_StartQuake #include "i_system.h" // I_GetPreciseTime, I_GetPrecisePrecision +#include "hu_stuff.h" // for the cecho #include "lua_script.h" #include "lua_libs.h" @@ -3879,6 +3880,14 @@ static int lib_getTimeMicros(lua_State *L) return 1; } +static int lib_startTitlecardCecho(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + HU_DoTitlecardCEcho(str); + + return 1; +} + static luaL_Reg lib[] = { {"print", lib_print}, {"chatprint", lib_chatprint}, @@ -4160,7 +4169,10 @@ static luaL_Reg lib[] = { {"K_InitBossHealthBar", lib_kInitBossHealthBar}, {"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar}, {"K_DeclareWeakspot", lib_kDeclareWeakspot}, - + + // hu_stuff technically? + {"HU_DoTitlecardCEcho", lib_startTitlecardCecho}, + {NULL, NULL} }; diff --git a/src/p_setup.c b/src/p_setup.c index bedb39c78..ebc3a40cd 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7515,6 +7515,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Clear CECHO messages HU_ClearCEcho(); + HU_ClearTitlecardCEcho(); if (mapheaderinfo[gamemap-1]->runsoc[0] != '#') P_RunSOC(mapheaderinfo[gamemap-1]->runsoc); From 9f1e5d08c248c8016c3f1b9c7405dfc0c32c5b0a Mon Sep 17 00:00:00 2001 From: Lat Date: Tue, 21 Feb 2023 20:56:45 +0100 Subject: [PATCH 02/60] Allow multiple lines in titlecard cecho, overwrite cecho in ACS instead of making a new action --- src/acs/call-funcs.cpp | 41 ---------------------------- src/acs/call-funcs.hpp | 2 -- src/acs/environment.cpp | 5 ---- src/hu_stuff.c | 59 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 780455f07..d3039dab9 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -555,28 +555,6 @@ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo (void)argV; (void)argC; - if (ACS_ActivatorIsLocal(thread) == true) - { - HU_SetCEchoDuration(5); - HU_DoCEcho(thread->printBuf.data()); - } - - thread->printBuf.drop(); - return false; -} - -/*-------------------------------------------------- - bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) - - One of the ACS wrappers for Titlecard CEcho. This - version only prints if the activator is a - display player. ---------------------------------------------------*/ -bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) -{ - (void)argV; - (void)argC; - if (ACS_ActivatorIsLocal(thread) == true) HU_DoTitlecardCEcho(thread->printBuf.data()); @@ -947,30 +925,11 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM (void)argV; (void)argC; - HU_SetCEchoDuration(5); - HU_DoCEcho(thread->printBuf.data()); - - thread->printBuf.drop(); - return false; -} - -/*-------------------------------------------------- - bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) - - One of the ACS wrappers for Titlecard CEcho. This - version prints for all players. ---------------------------------------------------*/ -bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) -{ - (void)argV; - (void)argC; - HU_DoTitlecardCEcho(thread->printBuf.data()); thread->printBuf.drop(); return false; } - /*-------------------------------------------------- bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 9e634c0d9..4bbe3eaec 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -55,7 +55,6 @@ bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSV bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); -bool CallFunc_EndPrintTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GameSpeed(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); @@ -66,7 +65,6 @@ bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACS bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); -bool CallFunc_EndPrintBoldTitlecard(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 28078967d..b3093e3ed 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -101,11 +101,6 @@ Environment::Environment() addCodeDataACS0(122, {"", 0, addCallFunc(CallFunc_PlayerScore)}); - // Lat: Titlecard CEcho. I'm not sure if I should be putting it here. - // @TODO: Confirm this is fine? - addCodeDataACS0( 123, {"", 0, addCallFunc(CallFunc_EndPrintTitlecard)}); - addCodeDataACS0( 124, {"", 0, addCallFunc(CallFunc_EndPrintBoldTitlecard)}); - // 136 to 137: Implemented by ACSVM // 157: Implemented by ACSVM diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 8521049a8..a68f4857b 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -161,7 +161,7 @@ static tic_t cechotimer = 0; static tic_t cechoduration = 5*TICRATE; static INT32 cechoflags = 0; -static char tcechotext[48]; // the text is wide so only 48 chars should do. +static char tcechotext[1024]; // buffer for the titlecard text static tic_t tcechotimer = 0; // goes up by 1 each frame this is active static tic_t tcechoduration = 0; // Set automatically @@ -2016,8 +2016,59 @@ static void HU_DrawTitlecardCEcho(void) { if (tcechotimer) { - INT32 w = V_TitleCardStringWidth(tcechotext); - V_DrawTitleCardString(160 -w/2, 90, tcechotext, 0, false, tcechotimer, TICRATE*4); + INT32 i = 0; + INT32 y = (BASEVIDHEIGHT/2)-16; + INT32 pnumlines = 0; + INT32 timeroffset = 0; + + char *line; + char *echoptr; + char temp[1024]; + + for (i = 0; tcechotext[i] != '\0'; ++i) + if (tcechotext[i] == '\\') + pnumlines++; + + y -= (pnumlines-1)*16; + + // Prevent crashing because I'm sick of this + if (y < 0) + { + CONS_Alert(CONS_WARNING, "CEcho contained too many lines, not displaying\n"); + cechotimer = 0; + return; + } + + strcpy(temp, tcechotext); + echoptr = &temp[0]; + + while (*echoptr != '\0') + { + INT32 w; + INT32 timer = (INT32)(tcechotimer - timeroffset); + + if (timer <= 0) + return; // we don't care. + + line = strchr(echoptr, '\\'); + + if (line == NULL) + break; + + *line = '\0'; + + w = V_TitleCardStringWidth(echoptr); + V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4); + + y += 32; + + // offset the timer for the next line. + timeroffset += strlen(echoptr); + + // set the ptr to the \0 we made and advance it because we don't want an empty string. + echoptr = line; + echoptr++; + } } } @@ -2636,6 +2687,8 @@ void HU_DoTitlecardCEcho(const char *msg) I_OutputMsg("%s\n", msg); // print to log strncpy(tcechotext, msg, sizeof(tcechotext)); + strncat(tcechotext, "\\", sizeof(tcechotext) - strlen(tcechotext) - 1); + cechotext[sizeof(tcechotext) - 1] = '\0'; tcechotimer = 1; tcechoduration = TICRATE*6 + strlen(tcechotext); } \ No newline at end of file From 2d063445a69f6c2d59a6e4035f75ff92e4dead64 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 26 Feb 2023 02:05:43 -0800 Subject: [PATCH 03/60] Automatically toggle director depending on number of players - Upon becoming a spectator: if there are any players who can be spectated, e.g. haven't finished the level, then turn on director cam. - Removes director cvar. --- src/d_netcmd.c | 4 ---- src/d_netcmd.h | 2 -- src/k_director.c | 21 ++++++++++++++++----- src/k_director.h | 2 ++ src/p_mobj.c | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e0491a74c..3085d4ee4 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -545,8 +545,6 @@ static CV_PossibleValue_t perfstats_cons_t[] = { }; consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL); -consvar_t cv_director = CVAR_INIT ("director", "Off", 0, CV_OnOff, NULL); - consvar_t cv_schedule = CVAR_INIT ("schedule", "On", CV_NETVAR|CV_CALL, CV_OnOff, Schedule_OnChange); consvar_t cv_automate = CVAR_INIT ("automate", "On", CV_NETVAR, CV_OnOff, NULL); @@ -1053,8 +1051,6 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_scr_width); CV_RegisterVar(&cv_scr_height); - CV_RegisterVar(&cv_director); - CV_RegisterVar(&cv_soundtest); CV_RegisterVar(&cv_invincmusicfade); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 61171d393..a126de023 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -128,8 +128,6 @@ extern consvar_t cv_sleep; extern consvar_t cv_perfstats; -extern consvar_t cv_director; - extern consvar_t cv_schedule; extern consvar_t cv_livestudioaudience; diff --git a/src/k_director.c b/src/k_director.c index 4f5737b4b..51158f8eb 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -25,6 +25,7 @@ void K_InitDirector(void) { INT32 playernum; + directorinfo.active = false; directorinfo.cooldown = SWITCHTIME; directorinfo.freeze = 0; directorinfo.attacker = 0; @@ -109,6 +110,11 @@ static boolean K_CanSwitchDirector(void) return false; } + if (!directorinfo.active) + { + return false; + } + return true; } @@ -218,11 +224,6 @@ void K_UpdateDirector(void) INT32 *displayplayerp = &displayplayers[0]; INT32 targetposition; - if (!cv_director.value) - { - return; - } - K_UpdateDirectorPositions(); if (directorinfo.cooldown > 0) { @@ -299,3 +300,13 @@ void K_UpdateDirector(void) break; } } + +void K_ToggleDirector(boolean active) +{ + if (directorinfo.active != active) + { + directorinfo.cooldown = 0; // switch immediately + } + + directorinfo.active = active; +} diff --git a/src/k_director.h b/src/k_director.h index 29bb01721..47b4b2265 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -12,6 +12,7 @@ extern "C" { extern struct directorinfo { + boolean active; // is view point switching enabled? tic_t cooldown; // how long has it been since we last switched? tic_t freeze; // when nonzero, fixed switch pending, freeze logic! INT32 attacker; // who to switch to when freeze delay elapses @@ -26,6 +27,7 @@ void K_InitDirector(void); void K_UpdateDirector(void); void K_DrawDirectorDebugger(void); void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source); +void K_ToggleDirector(boolean active); #ifdef __cplusplus } // extern "C" diff --git a/src/p_mobj.c b/src/p_mobj.c index 52e48da51..d9920a928 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -46,6 +46,7 @@ #include "k_collide.h" #include "k_objects.h" #include "k_grandprix.h" +#include "k_director.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); @@ -11827,6 +11828,22 @@ void P_SpawnPlayer(INT32 playernum) K_SpawnPlayerBattleBumpers(p); } } + + // I'm not refactoring the loop at the top of this file. + pcount = 0; + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (G_CouldView(i)) + { + pcount++; + } + } + + // Spectating when there is literally any other player in + // the level enables director cam. + // TODO: how do we support splitscreen? + K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); } void P_AfterPlayerSpawn(INT32 playernum) From 898cac9515c5a39ef7bf580f86597a5a32ff0730 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 26 Feb 2023 02:15:11 -0800 Subject: [PATCH 04/60] Replace F12 with standard button controls - Removes the function of the F12 key. - 'A' button to view next player, 'X' button to view previous player. - Pressing the A or X buttons temporarily disables director cam, meaning your view stays on that player. - R button to reenable director cam. - You can no longer use spectator movement to shake your viewpoint off of another player. --- src/g_game.c | 73 +++++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 0be648c30..bc99bdcf1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -61,6 +61,7 @@ #include "k_specialstage.h" #include "k_bot.h" #include "doomstat.h" +#include "k_director.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -1210,6 +1211,28 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) goto aftercmdinput; } + if (displayplayers[forplayer] != g_localplayers[forplayer]) + { + if (M_MenuButtonPressed(forplayer, MBT_A)) + { + G_AdjustView(ssplayer, 1, true); + K_ToggleDirector(false); + } + + if (M_MenuButtonPressed(forplayer, MBT_X)) + { + G_AdjustView(ssplayer, -1, true); + K_ToggleDirector(false); + } + + if (M_MenuButtonPressed(forplayer, MBT_R)) + { + K_ToggleDirector(true); + } + + goto aftercmdinput; + } + if (K_PlayerUsesBotMovement(player)) { // Bot ticcmd is generated by K_BuildBotTiccmd @@ -1398,16 +1421,6 @@ aftercmdinput: cmd->throwdir = -KART_FULLTURN; G_DoAnglePrediction(cmd, realtics, ssplayer, player); - - // Reset away view if a command is given. - if ((cmd->forwardmove || cmd->buttons) - && !r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) - { - // Call ViewpointSwitch hooks here. - // The viewpoint was forcibly changed. - LUA_HookViewpointSwitch(player, &players[consoleplayer], true); - displayplayers[0] = consoleplayer; - } } ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) @@ -1740,44 +1753,8 @@ boolean G_Responder(event_t *ev) return true; // chat ate the event } - // allow spy mode changes even during the demo - if (gamestate == GS_LEVEL && ev->type == ev_keydown - && (ev->data1 == KEY_F12 /*|| ev->data1 == gamecontrol[0][gc_viewpoint][0] || ev->data1 == gamecontrol[0][gc_viewpoint][1]*/)) - { - if (!demo.playback && (r_splitscreen || !netgame)) - g_localplayers[0] = consoleplayer; - else - { - G_AdjustView(1, 1, true); - - // change statusbar also if playing back demo - if (demo.quitafterplaying) - ST_changeDemoView(); - - return true; - } - } - if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam) { - /* - if (ev->data1 == gamecontrol[1][gc_viewpoint][0] || ev->data1 == gamecontrol[1][gc_viewpoint][1]) - { - G_AdjustView(2, 1, true); - return true; - } - else if (ev->data1 == gamecontrol[2][gc_viewpoint][0] || ev->data1 == gamecontrol[2][gc_viewpoint][1]) - { - G_AdjustView(3, 1, true); - return true; - } - else if (ev->data1 == gamecontrol[3][gc_viewpoint][0] || ev->data1 == gamecontrol[3][gc_viewpoint][1]) - { - G_AdjustView(4, 1, true); - return true; - } - */ - // Allow pausing if ( //ev->data1 == gamecontrol[0][gc_pause][0] @@ -2071,6 +2048,10 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) if (viewnum == 1 && demo.playback) consoleplayer = displayplayers[0]; + + // change statusbar also if playing back demo + if (demo.quitafterplaying) + ST_changeDemoView(); } // From 5f1e64d6d9050dc74e42e9cf88f46c944789401d Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 26 Feb 2023 02:27:25 -0800 Subject: [PATCH 05/60] Add spectator HUD for A, X, R buttons Old spectator HUD is preserved for empty game, "flying around" mode. --- src/k_hud.c | 32 ++++++++++++++++++++++++++++++++ src/st_stuff.c | 4 +++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/k_hud.c b/src/k_hud.c index 2edb1f3d5..d53a39d95 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4639,6 +4639,33 @@ K_drawMiniPing (void) } } +static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2]) +{ + const INT32 flags = V_SNAPTORIGHT | V_SLIDEIN; + const INT32 textflags = flags | V_6WIDTHSPACE | V_ALLOWLOWERCASE; + + const UINT8 anim_duration = 16; + const UINT8 anim = (leveltime % (anim_duration * 2)) < anim_duration; + + const INT32 x = BASEVIDWIDTH - 60; + const INT32 y = BASEVIDHEIGHT - 70 + (idx * 16); + + V_DrawScaledPatch(x, y - 4, flags, kp[anim]); + V_DrawRightAlignedThinString(x - 2, y, textflags, label); +} + +static void K_drawDirectorHUD(void) +{ + if (!LUA_HudEnabled(hud_textspectator)) + { + return; + } + + K_DrawDirectorButton(0, "Next Player", kp_button_a[0]); + K_DrawDirectorButton(1, "Prev Player", kp_button_x[0]); + K_DrawDirectorButton(2, "Director", kp_button_r); +} + static void K_drawDistributionDebugger(void) { itemroulette_t rouletteData = {0}; @@ -4957,6 +4984,11 @@ void K_drawKartHUD(void) K_drawMiniPing(); } + if (displayplayers[viewnum] != g_localplayers[viewnum]) + { + K_drawDirectorHUD(); + } + if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); diff --git a/src/st_stuff.c b/src/st_stuff.c index 01350c6ce..289a6c3a7 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1095,6 +1095,8 @@ void ST_preLevelTitleCardDrawer(void) // static void ST_overlayDrawer(void) { + const UINT8 viewnum = R_GetViewNumber(); + // hu_showscores = auto hide score/time/rings when tab rankings are shown if (!(hu_showscores && (netgame || multiplayer))) { @@ -1135,7 +1137,7 @@ static void ST_overlayDrawer(void) if (!hu_showscores && netgame && !mapreset) { - if (stplyr->spectator && LUA_HudEnabled(hud_textspectator)) + if (stplyr->spectator && displayplayers[viewnum] == g_localplayers[viewnum] && LUA_HudEnabled(hud_textspectator)) { const char *itemtxt = M_GetText("Item - Join Game"); From d855d96a1064ae54399117323e72a2ced72ca1b5 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 16 Jan 2023 20:14:23 -0600 Subject: [PATCH 06/60] hwr2: Add hardware 2D rendering --- src/CMakeLists.txt | 1 + src/cxxutil.hpp | 11 + src/f_finale.h | 4 + src/f_wipe.c | 163 +-- src/hwr2/CMakeLists.txt | 12 + src/hwr2/pass.cpp | 14 +- src/hwr2/pass.hpp | 13 +- src/hwr2/pass_blit_rect.cpp | 209 ++++ src/hwr2/pass_blit_rect.hpp | 91 ++ src/hwr2/pass_imgui.cpp | 126 +-- src/hwr2/pass_imgui.hpp | 12 +- src/hwr2/pass_manager.cpp | 169 ++++ src/hwr2/pass_manager.hpp | 60 ++ src/hwr2/pass_postprocess.cpp | 217 ++++ src/hwr2/pass_postprocess.hpp | 71 ++ src/hwr2/pass_resource_managers.cpp | 236 +++++ src/hwr2/pass_resource_managers.hpp | 129 +++ src/hwr2/pass_software.cpp | 262 ++--- src/hwr2/pass_software.hpp | 50 +- src/hwr2/pass_twodee.cpp | 954 ++++++++++++++++++ src/hwr2/pass_twodee.hpp | 116 +++ src/hwr2/twodee.cpp | 114 +++ src/hwr2/twodee.hpp | 280 +++++ src/i_video_common.cpp | 236 +++-- src/k_menudraw.c | 7 +- src/r_patch.cpp | 1 + src/rhi/gl3_core/gl3_core_rhi.cpp | 174 +++- src/rhi/gl3_core/gl3_core_rhi.hpp | 11 + src/rhi/gles2/gles2_rhi.cpp | 9 + src/rhi/gles2/gles2_rhi.hpp | 9 + src/rhi/handle.hpp | 9 + src/rhi/rhi.cpp | 30 +- src/rhi/rhi.hpp | 34 +- src/sdl/i_video.cpp | 4 + src/sdl/rhi_gl3_core_platform.cpp | 13 + src/sdl/rhi_gl3_core_platform.hpp | 9 + src/sdl/rhi_gles2_platform.cpp | 9 + src/sdl/rhi_gles2_platform.hpp | 9 + src/v_video.cpp | 594 +++++------ src/v_video.h | 8 + thirdparty/CMakeLists.txt | 1 + thirdparty/cpm-imgui.cmake | 1 + thirdparty/imgui_config/srb2_imconfig.h | 1 + thirdparty/stb_rect_pack/CMakeLists.txt | 3 + .../stb_rect_pack/include/stb_rect_pack.h | 623 ++++++++++++ thirdparty/stb_rect_pack/stb_rect_pack.c | 2 + 46 files changed, 4225 insertions(+), 886 deletions(-) create mode 100644 src/hwr2/pass_blit_rect.cpp create mode 100644 src/hwr2/pass_blit_rect.hpp create mode 100644 src/hwr2/pass_manager.cpp create mode 100644 src/hwr2/pass_manager.hpp create mode 100644 src/hwr2/pass_postprocess.cpp create mode 100644 src/hwr2/pass_postprocess.hpp create mode 100644 src/hwr2/pass_resource_managers.cpp create mode 100644 src/hwr2/pass_resource_managers.hpp create mode 100644 src/hwr2/pass_twodee.cpp create mode 100644 src/hwr2/pass_twodee.hpp create mode 100644 src/hwr2/twodee.cpp create mode 100644 src/hwr2/twodee.hpp create mode 100644 thirdparty/stb_rect_pack/CMakeLists.txt create mode 100644 thirdparty/stb_rect_pack/include/stb_rect_pack.h create mode 100644 thirdparty/stb_rect_pack/stb_rect_pack.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 898fcc789..0f7e9c479 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,6 +225,7 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN) target_sources(SRB2SDL2 PRIVATE discord.c stun.c) target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span) +target_link_libraries(SRB2SDL2 PRIVATE stb_rect_pack) target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis) target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite) target_link_libraries(SRB2SDL2 PRIVATE glad::glad) diff --git a/src/cxxutil.hpp b/src/cxxutil.hpp index 06f6f1adc..56b85c79b 100644 --- a/src/cxxutil.hpp +++ b/src/cxxutil.hpp @@ -162,6 +162,17 @@ struct Overload : Ts... { template Overload(Ts...) -> Overload; +inline void hash_combine(std::size_t& seed) +{} + +template +inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + hash_combine(seed, std::forward(rest)...); +} + } // namespace srb2 #endif // __SRB2_CXXUTIL_HPP__ diff --git a/src/f_finale.h b/src/f_finale.h index ca1108214..e2d4599a2 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -140,6 +140,10 @@ extern UINT16 curtttics; // extern boolean WipeInAction; +extern UINT8 g_wipetype; +extern UINT8 g_wipeframe; +extern boolean g_wipereverse; +extern boolean g_wipeskiprender; extern boolean WipeStageTitle; extern INT32 lastwipetic; diff --git a/src/f_wipe.c b/src/f_wipe.c index b15c4b171..98097b32c 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -90,6 +90,10 @@ UINT8 wipedefs[NUMWIPEDEFS] = { //-------------------------------------------------------------------------- boolean WipeInAction = false; +UINT8 g_wipetype = 0; +UINT8 g_wipeframe = 0; +boolean g_wipereverse = false; +boolean g_wipeskiprender = false; boolean WipeStageTitle = false; INT32 lastwipetic = 0; @@ -189,152 +193,6 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { return NULL; } -/** Wipe ticker - * - * \param fademask pixels to change - */ -static void F_DoWipe(fademask_t *fademask, lighttable_t *fadecolormap, boolean reverse) -{ - // Software mask wipe -- optimized; though it might not look like it! - // Okay, to save you wondering *how* this is more optimized than the simpler - // version that came before it... - // --- - // The previous code did two FixedMul calls for every single pixel on the - // screen, of which there are hundreds of thousands -- if not millions -- of. - // This worked fine for smaller screen sizes, but with excessively large - // (1920x1200) screens that meant 4 million+ calls out to FixedMul, and that - // would take /just/ long enough that fades would start to noticably lag. - // --- - // This code iterates over the fade mask's pixels instead of the screen's, - // and deals with drawing over each rectangular area before it moves on to - // the next pixel in the fade mask. As a result, it's more complex (and might - // look a little messy; sorry!) but it simultaneously runs at twice the speed. - // In addition, we precalculate all the X and Y positions that we need to draw - // from and to, so it uses a little extra memory, but again, helps it run faster. - // --- - // Sal: I kinda destroyed some of this code by introducing Genesis-style fades. - // A colormap can be provided in F_RunWipe, which the white/black values will be - // remapped to the appropriate entry in the fade colormap. - { - // wipe screen, start, end - UINT8 *w = wipe_scr; - const UINT8 *s = wipe_scr_start; - const UINT8 *e = wipe_scr_end; - - // first pixel for each screen - UINT8 *w_base = w; - const UINT8 *s_base = s; - const UINT8 *e_base = e; - - // mask data, end - UINT8 *transtbl; - const UINT8 *mask = fademask->mask; - const UINT8 *maskend = mask + fademask->size; - - // rectangle draw hints - UINT32 draw_linestart, draw_rowstart; - UINT32 draw_lineend, draw_rowend; - UINT32 draw_linestogo, draw_rowstogo; - - // rectangle coordinates, etc. - UINT16* scrxpos = (UINT16*)malloc((fademask->width + 1) * sizeof(UINT16)); - UINT16* scrypos = (UINT16*)malloc((fademask->height + 1) * sizeof(UINT16)); - UINT16 maskx, masky; - UINT32 relativepos; - - // --- - // Screw it, we do the fixed point math ourselves up front. - scrxpos[0] = 0; - for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx) - scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS; - scrxpos[fademask->width] = vid.width; - - scrypos[0] = 0; - for (relativepos = 0, masky = 1; masky < fademask->height; ++masky) - scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS; - scrypos[fademask->height] = vid.height; - // --- - - maskx = masky = 0; - do - { - UINT8 m = *mask; - - draw_rowstart = scrxpos[maskx]; - draw_rowend = scrxpos[maskx + 1]; - draw_linestart = scrypos[masky]; - draw_lineend = scrypos[masky + 1]; - - relativepos = (draw_linestart * vid.width) + draw_rowstart; - draw_linestogo = draw_lineend - draw_linestart; - - if (reverse) - m = ((pallen-1) - m); - - if (m == 0) - { - // shortcut - memcpy source to work - while (draw_linestogo--) - { - M_Memcpy(w_base+relativepos, (reverse ? e_base : s_base)+relativepos, draw_rowend-draw_rowstart); - relativepos += vid.width; - } - } - else if (m >= (pallen-1)) - { - // shortcut - memcpy target to work - while (draw_linestogo--) - { - M_Memcpy(w_base+relativepos, (reverse ? s_base : e_base)+relativepos, draw_rowend-draw_rowstart); - relativepos += vid.width; - } - } - else - { - // pointer to transtable that this mask would use - transtbl = transtables + ((9 - m)<= fademask->width) - ++masky, maskx = 0; - } while (++mask < maskend); - - free(scrxpos); - free(scrypos); - } -} #endif /** Save the "before" screen of a wipe. @@ -467,6 +325,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r // Init the wipe WipeInAction = true; + g_wipeskiprender = false; wipe_scr = screens[0]; // lastwipetic should either be 0 or the tic we last wiped @@ -494,7 +353,10 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers { - F_DoWipe(fmask, fcolor, reverse); + // F_DoWipe(fmask, fcolor, reverse); + g_wipetype = wipetype; + g_wipeframe = wipeframe - 1; + g_wipereverse = reverse; if (encorewiggle) { @@ -521,6 +383,12 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r I_FinishUpdate(); // page flip or blit buffer + if (rendermode != render_none) + { + // Skip subsequent renders until the end of the wipe to preserve the current frame. + g_wipeskiprender = true; + } + if (moviemode) M_SaveFrame(); @@ -528,6 +396,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r } WipeInAction = false; + g_wipeskiprender = false; if (fcolor) { diff --git a/src/hwr2/CMakeLists.txt b/src/hwr2/CMakeLists.txt index 504818105..34aa2186f 100644 --- a/src/hwr2/CMakeLists.txt +++ b/src/hwr2/CMakeLists.txt @@ -1,8 +1,20 @@ target_sources(SRB2SDL2 PRIVATE + pass_blit_rect.cpp + pass_blit_rect.hpp pass_imgui.cpp pass_imgui.hpp + pass_manager.cpp + pass_manager.hpp + pass_postprocess.cpp + pass_postprocess.hpp + pass_resource_managers.cpp + pass_resource_managers.hpp pass_software.cpp pass_software.hpp + pass_twodee.cpp + pass_twodee.hpp pass.cpp pass.hpp + twodee.cpp + twodee.hpp ) diff --git a/src/hwr2/pass.cpp b/src/hwr2/pass.cpp index 48b331492..d20be7294 100644 --- a/src/hwr2/pass.cpp +++ b/src/hwr2/pass.cpp @@ -1,3 +1,15 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "pass.hpp" -srb2::hwr2::Pass::~Pass() = default; +using namespace srb2; +using namespace srb2::hwr2; + +Pass::~Pass() = default; diff --git a/src/hwr2/pass.hpp b/src/hwr2/pass.hpp index 2556bf8f8..a745bd12b 100644 --- a/src/hwr2/pass.hpp +++ b/src/hwr2/pass.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_HWR2_PASS_HPP__ #define __SRB2_HWR2_PASS_HPP__ @@ -8,7 +17,9 @@ namespace srb2::hwr2 /// @brief A rendering pass which performs logic during each phase of a frame render. /// During rendering, all registered Pass's individual stages will be run together. -struct Pass { +class Pass +{ +public: virtual ~Pass(); /// @brief Perform rendering logic and create necessary GPU resources. diff --git a/src/hwr2/pass_blit_rect.cpp b/src/hwr2/pass_blit_rect.cpp new file mode 100644 index 000000000..56fb4f6dc --- /dev/null +++ b/src/hwr2/pass_blit_rect.cpp @@ -0,0 +1,209 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_blit_rect.hpp" + +#include + +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +namespace +{ +struct BlitVertex +{ + float x = 0.f; + float y = 0.f; + float z = 0.f; + float u = 0.f; + float v = 0.f; +}; +} // namespace + +static const BlitVertex kVerts[] = + {{-.5f, -.5f, 0.f, 0.f, 0.f}, {.5f, -.5f, 0.f, 1.f, 0.f}, {-.5f, .5f, 0.f, 0.f, 1.f}, {.5f, .5f, 0.f, 1.f, 1.f}}; + +static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2}; + +/// @brief Pipeline used for paletted source textures. Requires the texture and the palette texture. +static const PipelineDesc kPalettedPipelineDescription = { + PipelineProgram::kUnshadedPaletted, + {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, + {{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, + {{// R8 index texture + SamplerName::kSampler0, + // 256x1 palette texture + SamplerName::kSampler1}}, + std::nullopt, + {PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}}, + PrimitiveType::kTriangles, + CullMode::kNone, + FaceWinding::kCounterClockwise, + {0.f, 0.f, 0.f, 1.f}}; + +/// @brief Pipeline used for non-paletted source textures. +static const PipelineDesc kUnshadedPipelineDescription = { + PipelineProgram::kUnshaded, + {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, + {{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, + {{// RGB/A texture + SamplerName::kSampler0}}, + std::nullopt, + {PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}}, + PrimitiveType::kTriangles, + CullMode::kNone, + FaceWinding::kCounterClockwise, + {0.f, 0.f, 0.f, 1.f}}; + +BlitRectPass::BlitRectPass() : Pass() +{ +} + +BlitRectPass::BlitRectPass(bool output_clear) : Pass(), output_clear_(output_clear) +{ +} + +BlitRectPass::BlitRectPass(const std::shared_ptr& palette_mgr, bool output_clear) + : Pass(), output_clear_(output_clear), palette_mgr_(palette_mgr) +{ +} + +BlitRectPass::~BlitRectPass() = default; + +void BlitRectPass::prepass(Rhi& rhi) +{ + if (!pipeline_) + { + if (palette_mgr_) + { + pipeline_ = rhi.create_pipeline(kPalettedPipelineDescription); + } + else + { + pipeline_ = rhi.create_pipeline(kUnshadedPipelineDescription); + } + } + + if (!quad_vbo_) + { + quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable}); + quad_vbo_needs_upload_ = true; + } + + if (!quad_ibo_) + { + quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable}); + quad_ibo_needs_upload_ = true; + } + + if (!render_pass_) + { + render_pass_ = rhi.create_render_pass( + {std::nullopt, + PixelFormat::kRGBA8, + output_clear_ ? AttachmentLoadOp::kClear : AttachmentLoadOp::kLoad, + AttachmentStoreOp::kStore} + ); + } +} + +void BlitRectPass::transfer(Rhi& rhi, Handle ctx) +{ + if (quad_vbo_needs_upload_ && quad_vbo_) + { + rhi.update_buffer_contents(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); + quad_vbo_needs_upload_ = false; + } + + if (quad_ibo_needs_upload_ && quad_ibo_) + { + rhi.update_buffer_contents(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); + quad_ibo_needs_upload_ = false; + } + + float aspect = 1.0; + float output_aspect = 1.0; + if (output_correct_aspect_) + { + aspect = static_cast(texture_width_) / static_cast(texture_height_); + output_aspect = static_cast(output_width_) / static_cast(output_height_); + } + bool taller = aspect > output_aspect; + + std::array g1_uniforms = {{ + // Projection + std::array, 4> { + {{taller ? 1.f : 1.f / output_aspect, 0.f, 0.f, 0.f}, + {0.f, taller ? -1.f / (1.f / output_aspect) : -1.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {0.f, 0.f, 0.f, 1.f}}}, + }}; + + std::array g2_uniforms = { + {// ModelView + std::array, 4> { + {{taller ? 2.f : 2.f * aspect, 0.f, 0.f, 0.f}, + {0.f, taller ? 2.f * (1.f / aspect) : 2.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {0.f, 0.f, 0.f, 1.f}}}, + // Texcoord0 Transform + std::array, 3> { + {{1.f, 0.f, 0.f}, {0.f, output_flip_ ? -1.f : 1.f, 0.f}, {0.f, 0.f, 1.f}}}}}; + + uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms}); + uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); + + std::array vbs = {{{0, quad_vbo_}}}; + if (palette_mgr_) + { + std::array tbs = { + {{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, palette_mgr_->palette()}}}; + binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); + } + else + { + std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}}}; + binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); + } +} + +static constexpr const rhi::Color kClearColor = {0, 0, 0, 1}; + +void BlitRectPass::graphics(Rhi& rhi, Handle ctx) +{ + if (output_) + { + rhi.begin_render_pass(ctx, {render_pass_, output_, std::nullopt, kClearColor}); + } + else + { + rhi.begin_default_render_pass(ctx, output_clear_); + } + + rhi.bind_pipeline(ctx, pipeline_); + if (output_) + { + rhi.set_viewport(ctx, {0, 0, output_width_, output_height_}); + } + rhi.bind_uniform_set(ctx, 0, uniform_sets_[0]); + rhi.bind_uniform_set(ctx, 1, uniform_sets_[1]); + rhi.bind_binding_set(ctx, binding_set_); + rhi.bind_index_buffer(ctx, quad_ibo_); + rhi.draw_indexed(ctx, 6, 0); + rhi.end_render_pass(ctx); +} + +void BlitRectPass::postpass(Rhi& rhi) +{ +} diff --git a/src/hwr2/pass_blit_rect.hpp b/src/hwr2/pass_blit_rect.hpp new file mode 100644 index 000000000..d828812cd --- /dev/null +++ b/src/hwr2/pass_blit_rect.hpp @@ -0,0 +1,91 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_BLIT_RECT_HPP__ +#define __SRB2_HWR2_PASS_BLIT_RECT_HPP__ + +#include + +#include "../rhi/rhi.hpp" +#include "pass.hpp" +#include "pass_resource_managers.hpp" + +namespace srb2::hwr2 +{ + +/// @brief A render pass which blits a rect using a source texture or textures. +class BlitRectPass final : public Pass +{ + rhi::Handle pipeline_; + rhi::Handle texture_; + uint32_t texture_width_ = 0; + uint32_t texture_height_ = 0; + rhi::Handle output_; + uint32_t output_width_ = 0; + uint32_t output_height_ = 0; + bool output_correct_aspect_ = false; + bool output_clear_ = true; + bool output_flip_ = false; + rhi::Handle render_pass_; + rhi::Handle quad_vbo_; + rhi::Handle quad_ibo_; + std::array, 2> uniform_sets_; + rhi::Handle binding_set_; + + bool quad_vbo_needs_upload_ = false; + bool quad_ibo_needs_upload_ = false; + + // The presence of a palette manager indicates that the source texture will be paletted. This can't be changed. + std::shared_ptr palette_mgr_; + +public: + BlitRectPass(); + BlitRectPass(bool output_clear); + BlitRectPass(const std::shared_ptr& palette_mgr, bool output_clear); + virtual ~BlitRectPass(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + /// @brief Set the next blit texture. Don't call during graphics phase! + /// @param texture the texture to use when blitting + /// @param width texture width + /// @param height texture height + void set_texture(rhi::Handle texture, uint32_t width, uint32_t height) noexcept + { + texture_ = texture; + texture_width_ = width; + texture_height_ = height; + } + + /// @brief Set the next output texture. Don't call during graphics phase! + /// @param texture the texture to use as a color buffer + /// @param width texture width + /// @param height texture height + void set_output( + rhi::Handle color, + uint32_t width, + uint32_t height, + bool correct_aspect, + bool flip + ) noexcept + { + output_ = color; + output_width_ = width; + output_height_ = height; + output_correct_aspect_ = correct_aspect; + output_flip_ = flip; + } +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__ diff --git a/src/hwr2/pass_imgui.cpp b/src/hwr2/pass_imgui.cpp index 6d8e02861..e0d1d3cb6 100644 --- a/src/hwr2/pass_imgui.cpp +++ b/src/hwr2/pass_imgui.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "pass_imgui.hpp" #include @@ -8,48 +17,32 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -static const PipelineDesc kPipelineDesc = -{ +static const PipelineDesc kPipelineDesc = { PipelineProgram::kUnshaded, - { - { - {sizeof(ImDrawVert)} - }, - { - {VertexAttributeName::kPosition, 0, 0}, - {VertexAttributeName::kTexCoord0, 0, 12}, - {VertexAttributeName::kColor, 0, 24} - } - }, - {{ - {{UniformName::kProjection}}, - {{UniformName::kModelView, UniformName::kTexCoord0Transform}} - }}, - {{ - SamplerName::kSampler0 - }}, - PipelineDepthAttachmentDesc { - PixelFormat::kDepth16, - CompareFunc::kAlways, - true - }, - { - PixelFormat::kRGBA8, - BlendDesc { - BlendFactor::kSourceAlpha, - BlendFactor::kOneMinusSourceAlpha, - BlendFunction::kAdd, - BlendFactor::kOne, - BlendFactor::kOneMinusSourceAlpha, - BlendFunction::kAdd - }, - {true, true, true, true} - }, + {{{sizeof(ImDrawVert)}}, + {{VertexAttributeName::kPosition, 0, 0}, + {VertexAttributeName::kTexCoord0, 0, 12}, + {VertexAttributeName::kColor, 0, 24}}}, + {{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, + {{SamplerName::kSampler0}}, + PipelineDepthAttachmentDesc {PixelFormat::kDepth16, CompareFunc::kAlways, true}, + {PixelFormat::kRGBA8, + BlendDesc { + BlendFactor::kSourceAlpha, + BlendFactor::kOneMinusSourceAlpha, + BlendFunction::kAdd, + BlendFactor::kOne, + BlendFactor::kOneMinusSourceAlpha, + BlendFunction::kAdd}, + {true, true, true, true}}, PrimitiveType::kTriangles, CullMode::kNone, FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f} -}; + {0.f, 0.f, 0.f, 1.f}}; + +ImguiPass::ImguiPass() : Pass() +{ +} ImguiPass::~ImguiPass() = default; @@ -86,18 +79,10 @@ void ImguiPass::prepass(Rhi& rhi) for (auto list : draw_lists) { Handle vbo = rhi.create_buffer( - { - static_cast(list->VtxBuffer.size_in_bytes()), - BufferType::kVertexBuffer, - BufferUsage::kImmutable - } + {static_cast(list->VtxBuffer.size_in_bytes()), BufferType::kVertexBuffer, BufferUsage::kImmutable} ); Handle ibo = rhi.create_buffer( - { - static_cast(list->IdxBuffer.size_in_bytes()), - BufferType::kIndexBuffer, - BufferUsage::kImmutable - } + {static_cast(list->IdxBuffer.size_in_bytes()), BufferType::kIndexBuffer, BufferUsage::kImmutable} ); DrawList hwr2_list; @@ -126,13 +111,11 @@ void ImguiPass::prepass(Rhi& rhi) draw_cmd.v_offset = cmd.VtxOffset; draw_cmd.i_offset = cmd.IdxOffset; draw_cmd.elems = cmd.ElemCount; - draw_cmd.clip = - { + draw_cmd.clip = { static_cast(clip_min.x), static_cast((data->DisplaySize.y * data->FramebufferScale.y) - clip_max.y), static_cast(clip_max.x - clip_min.x), - static_cast(clip_max.y - clip_min.y) - }; + static_cast(clip_max.y - clip_min.y)}; hwr2_list.cmds.push_back(std::move(draw_cmd)); } draw_lists_.push_back(std::move(hwr2_list)); @@ -179,35 +162,20 @@ void ImguiPass::transfer(Rhi& rhi, Handle ctx) rhi.update_buffer_contents(ctx, ibo, 0, tcb::as_bytes(index_span)); // Uniform sets - std::array g1_uniforms = - {{ + std::array g1_uniforms = {{ // Projection - std::array, 4> - {{ - {2.f / vid.realwidth, 0.f, 0.f, 0.f}, - {0.f, 2.f / vid.realheight, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {-1.f, 1.f, 0.f, 1.f} - }}, - }}; - std::array g2_uniforms = - {{ - // ModelView - std::array, 4> - {{ - {1.f, 0.f, 0.f, 0.f}, - {0.f, -1.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {0.f, 0, 0.f, 1.f} - }}, - // Texcoord0 Transform - std::array, 3> - {{ - {1.f, 0.f, 0.f}, - {0.f, 1.f, 0.f}, - {0.f, 0.f, 1.f} - }} + std::array, 4> { + {{2.f / vid.realwidth, 0.f, 0.f, 0.f}, + {0.f, 2.f / vid.realheight, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {-1.f, 1.f, 0.f, 1.f}}}, }}; + std::array g2_uniforms = { + {// ModelView + std::array, 4> { + {{1.f, 0.f, 0.f, 0.f}, {0.f, -1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0, 0.f, 1.f}}}, + // Texcoord0 Transform + std::array, 3> {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}}}}; Handle us_1 = rhi.create_uniform_set(ctx, {g1_uniforms}); Handle us_2 = rhi.create_uniform_set(ctx, {g2_uniforms}); diff --git a/src/hwr2/pass_imgui.hpp b/src/hwr2/pass_imgui.hpp index 280e7fc9c..91d2afe20 100644 --- a/src/hwr2/pass_imgui.hpp +++ b/src/hwr2/pass_imgui.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_HWR2_PASS_IMGUI_HPP__ #define __SRB2_HWR2_PASS_IMGUI_HPP__ @@ -9,7 +18,7 @@ namespace srb2::hwr2 { -class ImguiPass : public Pass +class ImguiPass final : public Pass { struct DrawCmd { @@ -36,6 +45,7 @@ class ImguiPass : public Pass std::vector draw_lists_; public: + ImguiPass(); virtual ~ImguiPass(); virtual void prepass(rhi::Rhi& rhi) override; diff --git a/src/hwr2/pass_manager.cpp b/src/hwr2/pass_manager.cpp new file mode 100644 index 000000000..e79ef2725 --- /dev/null +++ b/src/hwr2/pass_manager.cpp @@ -0,0 +1,169 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_manager.hpp" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +namespace +{ + +class LambdaPass final : public Pass +{ + PassManager* mgr_; + std::function prepass_func_; + std::function postpass_func_; + +public: + LambdaPass(PassManager* mgr, std::function prepass_func); + LambdaPass( + PassManager* mgr, + std::function prepass_func, + std::function postpass_func + ); + virtual ~LambdaPass(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; +}; + +} // namespace + +LambdaPass::LambdaPass(PassManager* mgr, std::function prepass_func) + : mgr_(mgr), prepass_func_(prepass_func) +{ +} + +LambdaPass::LambdaPass( + PassManager* mgr, + std::function prepass_func, + std::function postpass_func +) + : mgr_(mgr), prepass_func_(prepass_func), postpass_func_(postpass_func) +{ +} + +LambdaPass::~LambdaPass() = default; + +void LambdaPass::prepass(Rhi& rhi) +{ + if (prepass_func_) + { + (prepass_func_)(*mgr_, rhi); + } +} + +void LambdaPass::transfer(Rhi&, Handle) +{ +} + +void LambdaPass::graphics(Rhi&, Handle) +{ +} + +void LambdaPass::postpass(Rhi& rhi) +{ + if (postpass_func_) + { + (postpass_func_)(*mgr_, rhi); + } +} + +PassManager::PassManager() = default; + +void PassManager::insert(const std::string& name, std::shared_ptr pass) +{ + SRB2_ASSERT(pass_by_name_.find(name) == pass_by_name_.end()); + + std::size_t index = passes_.size(); + passes_.push_back(PassManagerEntry {name, pass, true}); + pass_by_name_.insert({name, index}); +} + +void PassManager::insert(const std::string& name, std::function prepass_func) +{ + insert(std::forward(name), std::make_shared(LambdaPass {this, prepass_func})); +} + +void PassManager::insert( + const std::string& name, + std::function prepass_func, + std::function postpass_func +) +{ + insert( + std::forward(name), + std::make_shared(LambdaPass {this, prepass_func, postpass_func}) + ); +} + +void PassManager::set_pass_enabled(const std::string& name, bool enabled) +{ + SRB2_ASSERT(pass_by_name_.find(name) != pass_by_name_.end()); + + passes_[pass_by_name_[name]].enabled = enabled; +} + +std::weak_ptr PassManager::for_name(const std::string& name) +{ + auto itr = pass_by_name_.find(name); + if (itr == pass_by_name_.end()) + { + return std::weak_ptr(); + } + return passes_[itr->second].pass; +} + +void PassManager::render(Rhi& rhi) const +{ + if (passes_.empty()) + { + return; + } + + for (auto& pass : passes_) + { + if (pass.enabled) + { + pass.pass->prepass(rhi); + } + } + + Handle tc = rhi.begin_transfer(); + for (auto& pass : passes_) + { + if (pass.enabled) + { + pass.pass->transfer(rhi, tc); + } + } + rhi.end_transfer(tc); + + Handle gc = rhi.begin_graphics(); + for (auto& pass : passes_) + { + if (pass.enabled) + { + pass.pass->graphics(rhi, gc); + } + } + rhi.end_graphics(gc); + + for (auto& pass : passes_) + { + if (pass.enabled) + { + pass.pass->postpass(rhi); + } + } +} diff --git a/src/hwr2/pass_manager.hpp b/src/hwr2/pass_manager.hpp new file mode 100644 index 000000000..372db893e --- /dev/null +++ b/src/hwr2/pass_manager.hpp @@ -0,0 +1,60 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_MANAGER_HPP__ +#define __SRB2_HWR2_PASS_MANAGER_HPP__ + +#include +#include +#include +#include +#include +#include + +#include "../rhi/rhi.hpp" +#include "pass.hpp" + +namespace srb2::hwr2 +{ + +class PassManager +{ + struct PassManagerEntry + { + std::string name; + std::shared_ptr pass; + bool enabled; + }; + + std::unordered_map pass_by_name_; + std::vector passes_; + +public: + PassManager(); + PassManager(const PassManager&) = delete; + PassManager(PassManager&&) = delete; + PassManager& operator=(const PassManager&) = delete; + PassManager& operator=(PassManager&&) = delete; + + void insert(const std::string& name, std::shared_ptr pass); + void insert(const std::string& name, std::function prepass_func); + void insert( + const std::string& name, + std::function prepass_func, + std::function postpass_func + ); + std::weak_ptr for_name(const std::string& name); + void set_pass_enabled(const std::string& name, bool enabled); + + void render(rhi::Rhi& rhi) const; +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PASS_MANAGER_HPP__ diff --git a/src/hwr2/pass_postprocess.cpp b/src/hwr2/pass_postprocess.cpp new file mode 100644 index 000000000..89b1ae005 --- /dev/null +++ b/src/hwr2/pass_postprocess.cpp @@ -0,0 +1,217 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_postprocess.hpp" + +#include + +#include +#include + +#include "../f_finale.h" +#include "../w_wad.h" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +namespace +{ +struct PostprocessVertex +{ + float x; + float y; + float z; + float u; + float v; +}; + +static const PostprocessVertex kPostprocessVerts[] = + {{-.5f, -.5f, 0.f, 0.f, 0.f}, {.5f, -.5f, 0.f, 1.f, 0.f}, {-.5f, .5f, 0.f, 0.f, 1.f}, {.5f, .5f, 0.f, 1.f, 1.f}}; + +static const uint16_t kPostprocessIndices[] = {0, 1, 2, 1, 3, 2}; + +} // namespace + +static const PipelineDesc kWipePipelineDesc = { + PipelineProgram::kPostprocessWipe, + {{{sizeof(PostprocessVertex)}}, + { + {VertexAttributeName::kPosition, 0, 0}, + {VertexAttributeName::kTexCoord0, 0, 12}, + }}, + {{{{UniformName::kProjection}}}}, + {{SamplerName::kSampler0, SamplerName::kSampler1}}, + std::nullopt, + {PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}}, + PrimitiveType::kTriangles, + CullMode::kNone, + FaceWinding::kCounterClockwise, + {0.f, 0.f, 0.f, 1.f}}; + +PostprocessWipePass::PostprocessWipePass() : Pass() +{ +} + +PostprocessWipePass::~PostprocessWipePass() = default; + +void PostprocessWipePass::prepass(Rhi& rhi) +{ + if (!render_pass_) + { + render_pass_ = rhi.create_render_pass( + {std::nullopt, PixelFormat::kRGBA8, AttachmentLoadOp::kLoad, AttachmentStoreOp::kStore} + ); + } + + if (!pipeline_) + { + pipeline_ = rhi.create_pipeline(kWipePipelineDesc); + } + + if (!vbo_) + { + vbo_ = rhi.create_buffer({sizeof(PostprocessVertex) * 4, BufferType::kVertexBuffer, BufferUsage::kImmutable}); + upload_vbo_ = true; + } + if (!ibo_) + { + ibo_ = rhi.create_buffer({2 * 6, BufferType::kIndexBuffer, BufferUsage::kImmutable}); + upload_ibo_ = true; + } + + uint32_t wipe_type = g_wipetype; + uint32_t wipe_frame = g_wipeframe; + bool wipe_reverse = g_wipereverse; + if (wipe_type >= 100 || wipe_frame >= 100) + { + return; + } + + std::string lumpname = fmt::format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame); + lumpnum_t mask_lump = W_CheckNumForName(lumpname.c_str()); + if (mask_lump == LUMPERROR) + { + return; + } + + std::size_t mask_lump_size = W_LumpLength(mask_lump); + switch (mask_lump_size) + { + case 256000: + mask_w_ = 640; + mask_h_ = 400; + break; + case 64000: + mask_w_ = 320; + mask_h_ = 200; + break; + case 16000: + mask_w_ = 160; + mask_h_ = 100; + break; + case 4000: + mask_w_ = 80; + mask_h_ = 50; + break; + default: + return; + } + + mask_data_.clear(); + mask_data_.resize(mask_lump_size, 0); + W_ReadLump(mask_lump, mask_data_.data()); + if (wipe_reverse) + { + for (auto& b : mask_data_) + { + b = 32 - b; + } + } + + wipe_tex_ = rhi.create_texture({TextureFormat::kLuminance, mask_w_, mask_h_}); +} + +void PostprocessWipePass::transfer(Rhi& rhi, Handle ctx) +{ + if (wipe_tex_ == kNullHandle) + { + return; + } + + if (source_ == kNullHandle) + { + return; + } + + if (upload_vbo_) + { + rhi.update_buffer_contents(ctx, vbo_, 0, tcb::as_bytes(tcb::span(kPostprocessVerts))); + upload_vbo_ = false; + } + + if (upload_ibo_) + { + rhi.update_buffer_contents(ctx, ibo_, 0, tcb::as_bytes(tcb::span(kPostprocessIndices))); + upload_ibo_ = false; + } + + tcb::span data = tcb::as_bytes(tcb::span(mask_data_)); + rhi.update_texture(ctx, wipe_tex_, {0, 0, mask_w_, mask_h_}, PixelFormat::kR8, data); + + UniformVariant uniforms[] = { + {// Projection + std::array, 4> { + {{2.f, 0.f, 0.f, 0.f}, {0.f, 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}}}}; + us_ = rhi.create_uniform_set(ctx, {tcb::span(uniforms)}); + + VertexAttributeBufferBinding vbos[] = {{0, vbo_}}; + TextureBinding tx[] = {{SamplerName::kSampler0, source_}, {SamplerName::kSampler1, wipe_tex_}}; + bs_ = rhi.create_binding_set(ctx, pipeline_, {vbos, tx}); +} + +void PostprocessWipePass::graphics(Rhi& rhi, Handle ctx) +{ + if (wipe_tex_ == kNullHandle) + { + return; + } + + if (target_) + { + rhi.begin_render_pass(ctx, {render_pass_, target_, std::nullopt, {0, 0, 0, 1}}); + } + else + { + rhi.begin_default_render_pass(ctx, false); + } + + rhi.bind_pipeline(ctx, pipeline_); + if (target_) + { + rhi.set_viewport(ctx, {0, 0, target_w_, target_h_}); + } + rhi.bind_uniform_set(ctx, 0, us_); + rhi.bind_binding_set(ctx, bs_); + rhi.bind_index_buffer(ctx, ibo_); + rhi.draw_indexed(ctx, 6, 0); + + rhi.end_render_pass(ctx); +} + +void PostprocessWipePass::postpass(Rhi& rhi) +{ + if (wipe_tex_) + { + rhi.destroy_texture(wipe_tex_); + wipe_tex_ = kNullHandle; + } + + mask_data_.clear(); +} diff --git a/src/hwr2/pass_postprocess.hpp b/src/hwr2/pass_postprocess.hpp new file mode 100644 index 000000000..113d96296 --- /dev/null +++ b/src/hwr2/pass_postprocess.hpp @@ -0,0 +1,71 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_POSTPROCESS_HPP__ +#define __SRB2_HWR2_PASS_POSTPROCESS_HPP__ + +#include "pass.hpp" + +#include + +namespace srb2::hwr2 +{ + +class PostprocessWipePass final : public Pass +{ + rhi::Handle render_pass_; + rhi::Handle pipeline_; + rhi::Handle vbo_; + bool upload_vbo_ = false; + rhi::Handle ibo_; + bool upload_ibo_ = false; + rhi::Handle us_; + rhi::Handle bs_; + rhi::Handle wipe_tex_; + rhi::Handle source_; + uint32_t source_w_ = 0; + uint32_t source_h_ = 0; + rhi::Handle end_; + rhi::Handle target_; + uint32_t target_w_ = 0; + uint32_t target_h_ = 0; + + std::vector mask_data_; + uint32_t mask_w_ = 0; + uint32_t mask_h_ = 0; + +public: + PostprocessWipePass(); + virtual ~PostprocessWipePass(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + void set_source(rhi::Handle source, uint32_t width, uint32_t height) noexcept + { + source_ = source; + source_w_ = width; + source_h_ = height; + } + + void set_end(rhi::Handle end) noexcept { end_ = end; } + + void set_target(rhi::Handle target, uint32_t width, uint32_t height) noexcept + { + target_ = target; + target_w_ = width; + target_h_ = height; + } +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PASS_POSTPROCESS_HPP__ diff --git a/src/hwr2/pass_resource_managers.cpp b/src/hwr2/pass_resource_managers.cpp new file mode 100644 index 000000000..2f5e599c4 --- /dev/null +++ b/src/hwr2/pass_resource_managers.cpp @@ -0,0 +1,236 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_resource_managers.hpp" + +#include +#include + +#include "../v_video.h" +#include "../z_zone.h" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +FramebufferManager::FramebufferManager() : Pass() +{ +} + +FramebufferManager::~FramebufferManager() = default; + +void FramebufferManager::prepass(Rhi& rhi) +{ + uint32_t current_width = vid.width; + uint32_t current_height = vid.height; + + // Destroy the framebuffer textures if they exist and the video size changed + if (width_ != current_width || height_ != current_height) + { + if (main_colors_[0] != kNullHandle) + { + rhi.destroy_texture(main_colors_[0]); + main_colors_[0] = kNullHandle; + } + if (main_colors_[1] != kNullHandle) + { + rhi.destroy_texture(main_colors_[1]); + main_colors_[1] = kNullHandle; + } + if (main_depth_ != kNullHandle) + { + rhi.destroy_renderbuffer(main_depth_); + main_depth_ = kNullHandle; + } + + if (post_colors_[0] != kNullHandle) + { + rhi.destroy_texture(post_colors_[0]); + post_colors_[0] = kNullHandle; + } + if (post_colors_[1] != kNullHandle) + { + rhi.destroy_texture(post_colors_[1]); + post_colors_[1] = kNullHandle; + } + } + width_ = current_width; + height_ = current_height; + + // Recreate the framebuffer textures + if (main_colors_[0] == kNullHandle) + { + main_colors_[0] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } + if (main_colors_[1] == kNullHandle) + { + main_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } + if (main_depth_ == kNullHandle) + { + main_depth_ = rhi.create_renderbuffer({PixelFormat::kDepth16, current_width, current_height}); + } + + if (post_colors_[0] == kNullHandle) + { + post_colors_[0] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } + if (post_colors_[1] == kNullHandle) + { + post_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } +} + +void FramebufferManager::transfer(Rhi& rhi, Handle ctx) +{ +} + +void FramebufferManager::graphics(Rhi& rhi, Handle ctx) +{ +} + +void FramebufferManager::postpass(Rhi& rhi) +{ +} + +MainPaletteManager::MainPaletteManager() : Pass() +{ +} + +MainPaletteManager::~MainPaletteManager() = default; + +void MainPaletteManager::prepass(Rhi& rhi) +{ + if (!palette_) + { + palette_ = rhi.create_texture({TextureFormat::kRGBA, 256, 1}); + } +} + +void MainPaletteManager::transfer(Rhi& rhi, Handle ctx) +{ + std::array palette_32; + for (std::size_t i = 0; i < 256; i++) + { + palette_32[i] = V_GetColor(i).s; + } + rhi.update_texture(ctx, palette_, {0, 0, 256, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); +} + +void MainPaletteManager::graphics(Rhi& rhi, Handle ctx) +{ +} + +void MainPaletteManager::postpass(Rhi& rhi) +{ +} + +static uint32_t get_flat_size(lumpnum_t lump) +{ + SRB2_ASSERT(lump != LUMPERROR); + + std::size_t lumplength = W_LumpLength(lump); + if (lumplength == 0) + { + return 0; + } + + if ((lumplength & (lumplength - 1)) != 0) + { + // Lump length is not a power of two and therefore not a flat. + return 0; + } + uint32_t lumpsize = std::pow(2, std::log2(lumplength) / 2); + return lumpsize; +} + +FlatTextureManager::FlatTextureManager() : Pass() +{ +} + +FlatTextureManager::~FlatTextureManager() = default; + +void FlatTextureManager::prepass(Rhi& rhi) +{ +} + +void FlatTextureManager::transfer(Rhi& rhi, Handle ctx) +{ + std::vector> flat_data; + for (auto flat_lump : to_upload_) + { + flat_data.clear(); + Handle flat_texture = flats_[flat_lump]; + SRB2_ASSERT(flat_texture != kNullHandle); + std::size_t lump_length = W_LumpLength(flat_lump); + uint32_t flat_size = get_flat_size(flat_lump); + flat_data.reserve(flat_size * flat_size); + + const uint8_t* flat_memory = static_cast(W_CacheLumpNum(flat_lump, PU_PATCH)); + SRB2_ASSERT(flat_memory != nullptr); + + tcb::span flat_bytes = tcb::span(flat_memory, lump_length); + for (const uint8_t index : flat_bytes) + { + // The alpha/green channel is set to 0 if it's index 247; this is not usually used but fake floors can be + // masked sometimes, so we need to treat it as transparent when rendering them. + // See https://zdoom.org/wiki/Palette for remarks on fake 247 transparency + flat_data.push_back({index, index == 247 ? static_cast(0) : static_cast(255)}); + } + + // A flat size of 1 would end up being 2 bytes, so we need 2 more bytes to be unpack-aligned on texture upload + // Any other size would implicitly be aligned. + // Sure hope nobody tries to load any flats that are too big for the gpu! + if (flat_size == 1) + { + flat_data.push_back({0, 0}); + } + + tcb::span data_bytes = tcb::as_bytes(tcb::span(flat_data)); + rhi.update_texture(ctx, flat_texture, {0, 0, flat_size, flat_size}, rhi::PixelFormat::kRG8, data_bytes); + } + to_upload_.clear(); +} + +void FlatTextureManager::graphics(Rhi& rhi, Handle ctx) +{ +} + +void FlatTextureManager::postpass(Rhi& rhi) +{ +} + +Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, lumpnum_t lump) +{ + SRB2_ASSERT(lump != LUMPERROR); + + auto flat_itr = flats_.find(lump); + if (flat_itr != flats_.end()) + { + return flat_itr->second; + } + + uint32_t flat_size = get_flat_size(lump); + Handle new_tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, flat_size, flat_size}); + flats_.insert({lump, new_tex}); + to_upload_.push_back(lump); + return new_tex; +} + +Handle FlatTextureManager::find_indexed(lumpnum_t lump) const +{ + SRB2_ASSERT(lump != LUMPERROR); + + auto flat_itr = flats_.find(lump); + if (flat_itr != flats_.end()) + { + return flat_itr->second; + } + return kNullHandle; +} diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp new file mode 100644 index 000000000..4eee36b8c --- /dev/null +++ b/src/hwr2/pass_resource_managers.hpp @@ -0,0 +1,129 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__ +#define __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__ + +#include +#include +#include +#include + +#include "pass.hpp" + +namespace srb2::hwr2 +{ + +class FramebufferManager final : public Pass +{ + std::array, 2> main_colors_; + rhi::Handle main_depth_; + std::array, 2> post_colors_; + std::size_t main_index_ = 0; + std::size_t post_index_ = 0; + std::size_t width_ = 0; + std::size_t height_ = 0; + bool first_postprocess_ = true; + +public: + FramebufferManager(); + virtual ~FramebufferManager(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + /// @brief Swap the current and previous main colors. + void swap_main() noexcept { main_index_ = main_index_ == 0 ? 1 : 0; } + + /// @brief Swap the current and previous postprocess FB textures. Use between pass prepass phases to alternate. + void swap_post() noexcept + { + post_index_ = post_index_ == 0 ? 1 : 0; + first_postprocess_ = false; + } + + void reset_post() noexcept { first_postprocess_ = true; } + + rhi::Handle current_main_color() const noexcept { return main_colors_[main_index_]; } + rhi::Handle main_depth() const noexcept { return main_depth_; } + rhi::Handle previous_main_color() const noexcept { return main_colors_[1 - main_index_]; } + + rhi::Handle current_post_color() const noexcept { return post_colors_[post_index_]; } + + rhi::Handle previous_post_color() const noexcept + { + if (first_postprocess_) + { + return current_main_color(); + } + return post_colors_[1 - post_index_]; + }; + + std::size_t width() const noexcept { return width_; } + std::size_t height() const noexcept { return height_; } +}; + +class MainPaletteManager final : public Pass +{ + rhi::Handle palette_; + +public: + MainPaletteManager(); + virtual ~MainPaletteManager(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + rhi::Handle palette() const noexcept { return palette_; } +}; + +/* +A note to the reader: + +RHI/HWR2's architecture is intentionally decoupled in a data-oriented design fashion. Hash map lookups might technically +be slower than storing the RHI handle in a hypothetical Flat class object, but it frees us from worrying about the +validity of a given Handle when the RHI instance changes -- and it _can_, because this is designed to allow multiple +RHI backends -- because any given Pass must be disposed when the RHI changes. The implementation of I_FinishUpdate is +such that if the RHI is not the same as before, all passes must be reconstructed, and so we don't have to worry about +going around and resetting Handle references everywhere. If you're familiar with old GL, it's like decoupling GLmipmap_t +from patch_t. +*/ + +/// @brief Manages textures corresponding to specific flats indexed by lump number. +class FlatTextureManager final : public Pass +{ + std::unordered_map> flats_; + std::vector to_upload_; + std::vector> disposed_textures_; + +public: + FlatTextureManager(); + virtual ~FlatTextureManager(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + /// @brief Find the indexed texture for a given flat lump, or create one if it doesn't exist yet. Only call this + /// in prepass. + /// @param flat_lump + /// @return + rhi::Handle find_or_create_indexed(rhi::Rhi& rhi, lumpnum_t flat_lump); + + rhi::Handle find_indexed(lumpnum_t flat_lump) const; +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__ diff --git a/src/hwr2/pass_software.cpp b/src/hwr2/pass_software.cpp index a94b40169..aca51a615 100644 --- a/src/hwr2/pass_software.cpp +++ b/src/hwr2/pass_software.cpp @@ -1,10 +1,17 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "pass_software.hpp" -#include +#include "../i_video.h" +#include "../v_video.h" -#include - -#include "../cxxutil.hpp" #include "../d_netcmd.h" #ifdef HAVE_DISCORDRPC #include "../discord.h" @@ -13,82 +20,13 @@ #include "../m_avrecorder.h" #include "../st_stuff.h" #include "../s_sound.h" +#include "../st_stuff.h" #include "../v_video.h" using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -SoftwareBlitPass::~SoftwareBlitPass() = default; - -namespace -{ -struct SwBlitVertex -{ - float x = 0.f; - float y = 0.f; - float z = 0.f; - float u = 0.f; - float v = 0.f; -}; -} // namespace - -static const SwBlitVertex kVerts[] = -{ - {-.5f, -.5f, 0.f, 0.f, 0.f}, - {.5f, -.5f, 0.f, 1.f, 0.f}, - {-.5f, .5f, 0.f, 0.f, 1.f}, - {.5f, .5f, 0.f, 1.f, 1.f} -}; - -static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2}; - -static const PipelineDesc kPipelineDescription = -{ - PipelineProgram::kUnshadedPaletted, - { - { - {sizeof(SwBlitVertex)} - }, - { - {VertexAttributeName::kPosition, 0, 0}, - {VertexAttributeName::kTexCoord0, 0, 12} - } - }, - {{ - {{UniformName::kProjection}}, - {{UniformName::kModelView, UniformName::kTexCoord0Transform}} - }}, - {{ - // R8 index texture - SamplerName::kSampler0, - // 256x1 palette texture - SamplerName::kSampler1 - }}, - std::nullopt, - { - PixelFormat::kRGBA8, - std::nullopt, - {true, true, true, true} - }, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f} -}; - -static uint32_t next_pow_of_2(uint32_t in) -{ - in--; - in |= in >> 1; - in |= in >> 2; - in |= in >> 4; - in |= in >> 8; - in |= in >> 16; - in++; - return in; -} - static void temp_legacy_finishupdate_draws() { SCR_CalculateFPS(); @@ -100,8 +38,7 @@ static void temp_legacy_finishupdate_draws() if (cv_ticrate.value) SCR_DisplayTicRate(); - if (cv_showping.value && netgame && - ( consoleplayer != serverplayer || ! server_lagless )) + if (cv_showping.value && netgame && (consoleplayer != serverplayer || !server_lagless)) { if (server_lagless) { @@ -110,11 +47,8 @@ static void temp_legacy_finishupdate_draws() } else { - for ( - int player = 1; - player < MAXPLAYERS; - player++ - ){ + for (int player = 1; player < MAXPLAYERS; player++) + { if (D_IsPlayerHumanAndGaming(player)) { SCR_DisplayLocalPing(); @@ -142,149 +76,83 @@ static void temp_legacy_finishupdate_draws() #endif } -void SoftwareBlitPass::prepass(Rhi& rhi) +SoftwarePass::SoftwarePass() : Pass() { - if (!pipeline_) +} + +SoftwarePass::~SoftwarePass() = default; + +void SoftwarePass::prepass(Rhi& rhi) +{ + if (rendermode != render_soft) { - pipeline_ = rhi.create_pipeline(kPipelineDescription); + return; } - if (!quad_vbo_) - { - quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable}); - quad_vbo_needs_upload_ = true; - } - - if (!quad_ibo_) - { - quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable}); - quad_ibo_needs_upload_ = true; - } + // Render the player views... or not yet? Needs to be moved out of D_Display in d_main.c + // Assume it's already been done and vid.buffer contains the composited splitscreen view. + // In the future though, we will want to treat each player viewport separately for postprocessing. temp_legacy_finishupdate_draws(); - uint32_t vid_width = static_cast(vid.width); - uint32_t vid_height = static_cast(vid.height); - - if (screen_tex_ && (screen_tex_width_ < vid_width || screen_tex_height_ < vid_height)) + // Prepare RHI resources + if (screen_texture_ && (static_cast(width_) != vid.width || static_cast(height_) != vid.height)) { - rhi.destroy_texture(screen_tex_); - screen_tex_ = kNullHandle; + // Mode changed, recreate texture + rhi.destroy_texture(screen_texture_); + screen_texture_ = kNullHandle; } - if (!screen_tex_) + width_ = vid.width; + height_ = vid.height; + + if (!screen_texture_) { - screen_tex_width_ = next_pow_of_2(vid_width); - screen_tex_height_ = next_pow_of_2(vid_height); - screen_tex_ = rhi.create_texture({TextureFormat::kLuminance, screen_tex_width_, screen_tex_height_}); + screen_texture_ = rhi.create_texture({TextureFormat::kLuminance, width_, height_}); } - if (!palette_tex_) + // If the screen width won't fit the unpack alignment, we need to copy the screen. + if (width_ % kPixelRowUnpackAlignment > 0) { - palette_tex_ = rhi.create_texture({TextureFormat::kRGBA, 256, 1}); + std::size_t padded_width = (width_ + (kPixelRowUnpackAlignment - 1)) & !kPixelRowUnpackAlignment; + copy_buffer_.clear(); + copy_buffer_.reserve(padded_width * height_); + for (std::size_t y = 0; y < height_; y++) + { + for (std::size_t x = 0; x < width_; x++) + { + copy_buffer_.push_back(vid.buffer[(width_ * y) + x]); + } + + // Padding to unpack alignment + for (std::size_t i = 0; i < padded_width - width_; i++) + { + copy_buffer_.push_back(0); + } + } } } -void SoftwareBlitPass::upload_screen(Rhi& rhi, Handle ctx) +void SoftwarePass::transfer(Rhi& rhi, Handle ctx) { - rhi::Rect screen_rect = { - 0, - 0, - static_cast(vid.width), - static_cast(vid.height) - }; - - tcb::span screen_span = tcb::span(vid.buffer, static_cast(vid.width * vid.height)); - rhi.update_texture(ctx, screen_tex_, screen_rect, rhi::PixelFormat::kR8, tcb::as_bytes(screen_span)); -} - -void SoftwareBlitPass::upload_palette(Rhi& rhi, Handle ctx) -{ - // Unfortunately, pMasterPalette must be swizzled to get a linear layout. - // Maybe some adjustments to palette storage can make this a straight upload. - std::array palette_32; - for (size_t i = 0; i < 256; i++) + // Upload screen + tcb::span screen_span; + if (width_ % kPixelRowUnpackAlignment > 0) { - palette_32[i] = pMasterPalette[i].s; + screen_span = tcb::as_bytes(tcb::span(copy_buffer_)); } - rhi.update_texture(ctx, palette_tex_, {0, 0, 256, 1}, rhi::PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); -} - -void SoftwareBlitPass::transfer(Rhi& rhi, Handle ctx) -{ - if (quad_vbo_needs_upload_ && quad_vbo_) + else { - rhi.update_buffer_contents(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); - quad_vbo_needs_upload_ = false; + screen_span = tcb::as_bytes(tcb::span(vid.buffer, width_ * height_)); } - if (quad_ibo_needs_upload_ && quad_ibo_) - { - rhi.update_buffer_contents(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); - quad_ibo_needs_upload_ = false; - } - - upload_screen(rhi, ctx); - upload_palette(rhi, ctx); - - // Calculate aspect ratio for black borders - float aspect = static_cast(vid.width) / static_cast(vid.height); - float real_aspect = static_cast(vid.realwidth) / static_cast(vid.realheight); - bool taller = aspect > real_aspect; - - std::array g1_uniforms = {{ - // Projection - std::array, 4> {{ - {taller ? 1.f : 1.f / real_aspect, 0.f, 0.f, 0.f}, - {0.f, taller ? -1.f / (1.f / real_aspect) : -1.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {0.f, 0.f, 0.f, 1.f} - }}, - }}; - - std::array g2_uniforms = - {{ - // ModelView - std::array, 4> - {{ - {taller ? 2.f : 2.f * aspect, 0.f, 0.f, 0.f}, - {0.f, taller ? 2.f * (1.f / aspect) : 2.f, 0.f, 0.f}, - {0.f, 0.f, 1.f, 0.f}, - {0.f, 0.f, 0.f, 1.f} - }}, - // Texcoord0 Transform - std::array, 3> - {{ - {vid.width / static_cast(screen_tex_width_), 0.f, 0.f}, - {0.f, vid.height / static_cast(screen_tex_height_), 0.f}, - {0.f, 0.f, 1.f} - }} - }}; - - uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms}); - uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); - - std::array vbs = {{{0, quad_vbo_}}}; - std::array tbs = {{ - {rhi::SamplerName::kSampler0, screen_tex_}, - {rhi::SamplerName::kSampler1, palette_tex_} - }}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); + rhi.update_texture(ctx, screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span); } -void SoftwareBlitPass::graphics(Rhi& rhi, Handle ctx) +void SoftwarePass::graphics(Rhi& rhi, Handle ctx) { - rhi.begin_default_render_pass(ctx, true); - rhi.bind_pipeline(ctx, pipeline_); - rhi.bind_uniform_set(ctx, 0, uniform_sets_[0]); - rhi.bind_uniform_set(ctx, 1, uniform_sets_[1]); - rhi.bind_binding_set(ctx, binding_set_); - rhi.bind_index_buffer(ctx, quad_ibo_); - rhi.draw_indexed(ctx, 6, 0); - rhi.end_render_pass(ctx); } -void SoftwareBlitPass::postpass(Rhi& rhi) +void SoftwarePass::postpass(Rhi& rhi) { - // no-op } diff --git a/src/hwr2/pass_software.hpp b/src/hwr2/pass_software.hpp index f36c82973..4e7b02405 100644 --- a/src/hwr2/pass_software.hpp +++ b/src/hwr2/pass_software.hpp @@ -1,44 +1,46 @@ -#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP__ -#define __SRB2_HWR2_PASS_SOFTWARE_HPP__ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- -#include +#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP_ +#define __SRB2_HWR2_PASS_SOFTWARE_HPP_ + +#include +#include -#include "../rhi/rhi.hpp" #include "pass.hpp" namespace srb2::hwr2 { -class SoftwareBlitPass : public Pass +/// @brief Renders software player views in prepass and uploads the result to a texture in transfer. +class SoftwarePass final : public Pass { - rhi::Handle pipeline_; - rhi::Handle screen_tex_; - rhi::Handle palette_tex_; - rhi::Handle quad_vbo_; - rhi::Handle quad_ibo_; - std::array, 2> uniform_sets_; - rhi::Handle binding_set_; + rhi::Handle screen_texture_; + uint32_t width_ = 0; + uint32_t height_ = 0; - uint32_t screen_tex_width_ = 0; - uint32_t screen_tex_height_ = 0; - bool quad_vbo_needs_upload_ = false; - bool quad_ibo_needs_upload_ = false; - - void upload_screen(rhi::Rhi& rhi, rhi::Handle ctx); - void upload_palette(rhi::Rhi& rhi, rhi::Handle ctx); + // Used to ensure the row spans are aligned on the unpack boundary for weird resolutions + // Any resolution with a width divisible by 4 doesn't need this, but e.g. 1366x768 needs the intermediary copy + std::vector copy_buffer_; public: - virtual ~SoftwareBlitPass(); + SoftwarePass(); + virtual ~SoftwarePass(); virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + + rhi::Handle screen_texture() const noexcept { return screen_texture_; } }; } // namespace srb2::hwr2 -#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__ +#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP_ diff --git a/src/hwr2/pass_twodee.cpp b/src/hwr2/pass_twodee.cpp new file mode 100644 index 000000000..469fdcd2a --- /dev/null +++ b/src/hwr2/pass_twodee.cpp @@ -0,0 +1,954 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_twodee.hpp" + +#include + +#include + +#include "../r_patch.h" +#include "../v_video.h" +#include "../z_zone.h" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +namespace +{ + +struct AtlasEntry +{ + uint32_t x; + uint32_t y; + uint32_t w; + uint32_t h; + + uint32_t trim_x; + uint32_t trim_y; + uint32_t orig_w; + uint32_t orig_h; +}; + +struct Atlas +{ + Atlas() = default; + Atlas(Atlas&&) = default; + + Handle tex; + uint32_t tex_width; + uint32_t tex_height; + std::unordered_map entries; + + std::unique_ptr rp_ctx {nullptr}; + std::unique_ptr rp_nodes {nullptr}; + + Atlas& operator=(Atlas&&) = default; +}; + +} // namespace + +struct srb2::hwr2::TwodeePassData +{ + Handle default_tex; + Handle palette_tex; + Handle default_colormap_tex; + std::vector patch_atlases; + std::unordered_map patch_lookup; + std::vector patches_to_upload; + std::unordered_map> colormaps; + std::vector colormaps_to_upload; + std::unordered_map> pipelines; + bool upload_default_tex = false; +}; + +std::shared_ptr srb2::hwr2::make_twodee_pass_data() +{ + return std::make_shared(); +} + +TwodeePass::TwodeePass() : Pass() +{ +} + +TwodeePass::~TwodeePass() = default; + +static constexpr const uint32_t kVboInitSize = 32768; +static constexpr const uint32_t kIboInitSize = 4096; + +static Rect trimmed_patch_dim(const patch_t* patch); + +static void create_atlas(Rhi& rhi, TwodeePassData& pass_data) +{ + Atlas new_atlas; + new_atlas.tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, 2048, 2048}); + new_atlas.tex_width = 2048; + new_atlas.tex_height = 2048; + new_atlas.rp_ctx = std::make_unique(); + new_atlas.rp_nodes = std::make_unique(4096); + for (size_t i = 0; i < 4096; i++) + { + new_atlas.rp_nodes[i] = {}; + } + stbrp_init_target(new_atlas.rp_ctx.get(), 2048, 2048, new_atlas.rp_nodes.get(), 4096); + // it is CRITICALLY important that the atlas is MOVED, not COPIED, otherwise the node ptrs will be broken + pass_data.patch_atlases.push_back(std::move(new_atlas)); +} + +static void pack_patches(Rhi& rhi, TwodeePassData& pass_data, tcb::span patches) +{ + // Prepare stbrp rects for patches to be loaded. + std::vector rects; + for (size_t i = 0; i < patches.size(); i++) + { + const patch_t* patch = patches[i]; + Rect trimmed_rect = trimmed_patch_dim(patch); + stbrp_rect rect {}; + rect.id = i; + rect.w = trimmed_rect.w; + rect.h = trimmed_rect.h; + rects.push_back(std::move(rect)); + } + + while (rects.size() > 0) + { + if (pass_data.patch_atlases.size() == 0) + { + create_atlas(rhi, pass_data); + } + + for (size_t atlas_index = 0; atlas_index < pass_data.patch_atlases.size(); atlas_index++) + { + auto& atlas = pass_data.patch_atlases[atlas_index]; + + stbrp_pack_rects(atlas.rp_ctx.get(), rects.data(), rects.size()); + for (auto itr = rects.begin(); itr != rects.end();) + { + auto& rect = *itr; + if (rect.was_packed) + { + AtlasEntry entry; + const patch_t* patch = patches[rect.id]; + // TODO prevent unnecessary recalculation of trim? + Rect trimmed_rect = trimmed_patch_dim(patch); + entry.x = static_cast(rect.x); + entry.y = static_cast(rect.y); + entry.w = static_cast(rect.w); + entry.h = static_cast(rect.h); + entry.trim_x = static_cast(trimmed_rect.x); + entry.trim_y = static_cast(trimmed_rect.y); + entry.orig_w = static_cast(patch->width); + entry.orig_h = static_cast(patch->height); + atlas.entries.insert_or_assign(patch, std::move(entry)); + pass_data.patch_lookup.insert_or_assign(patch, atlas_index); + pass_data.patches_to_upload.push_back(patch); + rects.erase(itr); + continue; + } + ++itr; + } + + // If we still have rects to pack, and we're at the last atlas, create another atlas. + // TODO This could end up in an infinite loop if the patches are bigger than an atlas. Such patches need to + // be loaded as individual RHI textures instead. + if (atlas_index == pass_data.patch_atlases.size() - 1 && rects.size() > 0) + { + create_atlas(rhi, pass_data); + } + } + } +} + +/// @brief Derive the subrect of the given patch with empty columns and rows excluded. +static Rect trimmed_patch_dim(const patch_t* patch) +{ + bool minx_found = false; + int32_t minx = 0; + int32_t maxx = 0; + int32_t miny = patch->height; + int32_t maxy = 0; + for (int32_t x = 0; x < patch->width; x++) + { + const int32_t columnofs = patch->columnofs[x]; + const column_t* column = reinterpret_cast(patch->columns + columnofs); + + // If the first pole is empty (topdelta = 255), there are no pixels in this column + if (!minx_found && column->topdelta == 0xFF) + { + // Thus, the minx is at least one higher than the current column. + minx = x + 1; + continue; + } + minx_found = true; + + if (minx_found && column->topdelta != 0xFF) + { + maxx = x; + } + + miny = std::min(static_cast(column->topdelta), miny); + + int32_t prevdelta = 0; + int32_t topdelta = 0; + while (column->topdelta != 0xFF) + { + topdelta = column->topdelta; + + // Tall patches hack + if (topdelta <= prevdelta) + { + topdelta += prevdelta; + } + prevdelta = topdelta; + + maxy = std::max(topdelta + column->length, maxy); + + column = reinterpret_cast(reinterpret_cast(column) + column->length + 4); + } + } + + maxx += 1; + maxx = std::max(minx, maxx); + maxy = std::max(miny, maxy); + + return {minx, miny, static_cast(maxx - minx), static_cast(maxy - miny)}; +} + +static void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& out) +{ + Rect trimmed_rect = trimmed_patch_dim(patch); + if (trimmed_rect.w % 2 > 0) + { + // In order to force 4-byte row alignment, an extra column is added to the image data. + // Look up GL_UNPACK_ALIGNMENT (which defaults to 4 bytes) + trimmed_rect.w += 1; + } + out.clear(); + // 2 bytes per pixel; 1 for the color index, 1 for the alpha. (RG8) + out.resize(trimmed_rect.w * trimmed_rect.h * 2, 0); + for (int32_t x = 0; x < static_cast(trimmed_rect.w) && x < (patch->width - trimmed_rect.x); x++) + { + const int32_t columnofs = patch->columnofs[x + trimmed_rect.x]; + const column_t* column = reinterpret_cast(patch->columns + columnofs); + + int32_t prevdelta = 0; + int32_t topdelta = 0; + while (column->topdelta != 0xFF) + { + topdelta = column->topdelta; + // prevdelta is used to implement tall patches hack + if (topdelta <= prevdelta) + { + topdelta += prevdelta; + } + + prevdelta = topdelta; + const uint8_t* source = reinterpret_cast(column) + 3; + int32_t count = column->length; // is this byte order safe...? + + for (int32_t i = 0; i < count; i++) + { + int32_t output_y = topdelta + i - trimmed_rect.y; + if (output_y < 0) + { + continue; + } + if (output_y >= static_cast(trimmed_rect.h)) + { + break; + } + size_t pixel_index = (output_y * trimmed_rect.w + x) * 2; + out[pixel_index + 0] = source[i]; // index in luminance/red channel + out[pixel_index + 1] = 0xFF; // alpha/green value of 1 + } + column = reinterpret_cast(reinterpret_cast(column) + column->length + 4); + } + } +} + +static TwodeePipelineKey pipeline_key_for_cmd(const Draw2dCmd& cmd) +{ + return {hwr2::get_blend_mode(cmd), hwr2::is_draw_lines(cmd)}; +} + +static PipelineDesc make_pipeline_desc(TwodeePipelineKey key) +{ + constexpr const VertexInputDesc kTwodeeVertexInput = { + {{sizeof(TwodeeVertex)}}, + {{VertexAttributeName::kPosition, 0, 0}, + {VertexAttributeName::kTexCoord0, 0, 12}, + {VertexAttributeName::kColor, 0, 20}}}; + BlendDesc blend_desc; + switch (key.blend) + { + case Draw2dBlend::kModulate: + blend_desc.source_factor_color = BlendFactor::kSourceAlpha; + blend_desc.dest_factor_color = BlendFactor::kOneMinusSourceAlpha; + blend_desc.color_function = BlendFunction::kAdd; + blend_desc.source_factor_alpha = BlendFactor::kOne; + blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + blend_desc.alpha_function = BlendFunction::kAdd; + break; + case Draw2dBlend::kAdditive: + blend_desc.source_factor_color = BlendFactor::kSourceAlpha; + blend_desc.dest_factor_color = BlendFactor::kOne; + blend_desc.color_function = BlendFunction::kAdd; + blend_desc.source_factor_alpha = BlendFactor::kOne; + blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + blend_desc.alpha_function = BlendFunction::kAdd; + break; + case Draw2dBlend::kSubtractive: + blend_desc.source_factor_color = BlendFactor::kSourceAlpha; + blend_desc.dest_factor_color = BlendFactor::kOne; + blend_desc.color_function = BlendFunction::kSubtract; + blend_desc.source_factor_alpha = BlendFactor::kOne; + blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + blend_desc.alpha_function = BlendFunction::kAdd; + break; + case Draw2dBlend::kReverseSubtractive: + blend_desc.source_factor_color = BlendFactor::kSourceAlpha; + blend_desc.dest_factor_color = BlendFactor::kOne; + blend_desc.color_function = BlendFunction::kReverseSubtract; + blend_desc.source_factor_alpha = BlendFactor::kOne; + blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + blend_desc.alpha_function = BlendFunction::kAdd; + break; + case Draw2dBlend::kInvertDest: + blend_desc.source_factor_color = BlendFactor::kOne; + blend_desc.dest_factor_color = BlendFactor::kOne; + blend_desc.color_function = BlendFunction::kSubtract; + blend_desc.source_factor_alpha = BlendFactor::kZero; + blend_desc.dest_factor_alpha = BlendFactor::kDestAlpha; + blend_desc.alpha_function = BlendFunction::kAdd; + break; + } + + return { + PipelineProgram::kUnshadedPaletted, + kTwodeeVertexInput, + {{{{UniformName::kProjection}}, + {{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0IsIndexedAlpha}}}}, + {{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}}, + std::nullopt, + {PixelFormat::kRGBA8, blend_desc, {true, true, true, true}}, + key.lines ? PrimitiveType::kLines : PrimitiveType::kTriangles, + CullMode::kNone, + FaceWinding::kCounterClockwise, + {0.f, 0.f, 0.f, 1.f}}; +} + +static void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd, TwodeePassData* data) +{ + // Patch quads are clipped according to the patch's atlas entry + if (cmd.patch == nullptr) + { + return; + } + + std::size_t atlas_index = data->patch_lookup[cmd.patch]; + auto& atlas = data->patch_atlases[atlas_index]; + auto& entry = atlas.entries[cmd.patch]; + + // Rewrite the vertex data completely. + // The UVs of the trimmed patch in atlas UV space. + const float atlas_umin = static_cast(entry.x) / atlas.tex_width; + const float atlas_umax = static_cast(entry.x + entry.w) / atlas.tex_width; + const float atlas_vmin = static_cast(entry.y) / atlas.tex_height; + const float atlas_vmax = static_cast(entry.y + entry.h) / atlas.tex_height; + + // The UVs of the trimmed patch in untrimmed UV space. + // The command's UVs are in untrimmed UV space. + const float trim_umin = static_cast(entry.trim_x) / entry.orig_w; + const float trim_umax = static_cast(entry.trim_x + entry.w) / entry.orig_w; + const float trim_vmin = static_cast(entry.trim_y) / entry.orig_h; + const float trim_vmax = static_cast(entry.trim_y + entry.h) / entry.orig_h; + + // Calculate positions + const float cmd_xrange = cmd.xmax - cmd.xmin; + const float cmd_yrange = cmd.ymax - cmd.ymin; + const float clipped_xmin = cmd.clip ? std::clamp(cmd.xmin, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmin; + const float clipped_xmax = cmd.clip ? std::clamp(cmd.xmax, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmax; + const float clipped_ymin = cmd.clip ? std::clamp(cmd.ymin, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymin; + const float clipped_ymax = cmd.clip ? std::clamp(cmd.ymax, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymax; + const float trimmed_xmin = cmd.xmin + trim_umin * cmd_xrange; + const float trimmed_xmax = cmd.xmax - (1.f - trim_umax) * cmd_xrange; + const float trimmed_ymin = cmd.ymin + trim_vmin * cmd_yrange; + const float trimmed_ymax = cmd.ymax - (1.f - trim_vmax) * cmd_yrange; + const float trimmed_xrange = trimmed_xmax - trimmed_xmin; + const float trimmed_yrange = trimmed_ymax - trimmed_ymin; + float clipped_trimmed_xmin = std::max(clipped_xmin, trimmed_xmin); + float clipped_trimmed_xmax = std::min(clipped_xmax, trimmed_xmax); + float clipped_trimmed_ymin = std::max(clipped_ymin, trimmed_ymin); + float clipped_trimmed_ymax = std::min(clipped_ymax, trimmed_ymax); + clipped_trimmed_xmin = std::min(clipped_trimmed_xmin, clipped_trimmed_xmax); + clipped_trimmed_ymin = std::min(clipped_trimmed_ymin, clipped_trimmed_ymax); + + // Calculate UVs + // Start from trimmed dimensions as 0..1 and clip UVs based on that + // UVs in trimmed UV space (if clipped_xmin = trimmed_xmin, it'll be 0) + float clipped_umin; + float clipped_umax; + float clipped_vmin; + float clipped_vmax; + + if (cmd.flip) + { + clipped_umin = std::max(0.f, 1.f - (clipped_trimmed_xmin - trimmed_xmin) / trimmed_xrange); + clipped_umax = std::min(1.f, (trimmed_xmax - clipped_trimmed_xmax) / trimmed_xrange); + } + else + { + clipped_umin = std::min(1.f, (clipped_trimmed_xmin - trimmed_xmin) / trimmed_xrange); + clipped_umax = std::max(0.f, 1.f - (trimmed_xmax - clipped_trimmed_xmax) / trimmed_xrange); + } + + if (cmd.vflip) + { + clipped_vmin = std::max(0.f, 1.f - (clipped_trimmed_ymin - trimmed_ymin) / trimmed_yrange); + clipped_vmax = std::min(1.f, (trimmed_ymax - clipped_trimmed_ymax) / trimmed_yrange); + } + else + { + clipped_vmin = std::min(1.f, 0.f + (clipped_trimmed_ymin - trimmed_ymin) / trimmed_yrange); + clipped_vmax = std::max(0.f, 1.f - (trimmed_ymax - clipped_trimmed_ymax) / trimmed_yrange); + } + + // convert from trimmed UV space to atlas space + clipped_umin = (atlas_umax - atlas_umin) * clipped_umin + atlas_umin; + clipped_umax = (atlas_umax - atlas_umin) * clipped_umax + atlas_umin; + clipped_vmin = (atlas_vmax - atlas_vmin) * clipped_vmin + atlas_vmin; + clipped_vmax = (atlas_vmax - atlas_vmin) * clipped_vmax + atlas_vmin; + + std::size_t vtx_offs = cmd.begin_index; + // Vertex order is always min/min, max/min, max/max, min/max + list.vertices[vtx_offs + 0].x = clipped_trimmed_xmin; + list.vertices[vtx_offs + 0].y = clipped_trimmed_ymin; + list.vertices[vtx_offs + 0].u = clipped_umin; + list.vertices[vtx_offs + 0].v = clipped_vmin; + list.vertices[vtx_offs + 1].x = clipped_trimmed_xmax; + list.vertices[vtx_offs + 1].y = clipped_trimmed_ymin; + list.vertices[vtx_offs + 1].u = clipped_umax; + list.vertices[vtx_offs + 1].v = clipped_vmin; + list.vertices[vtx_offs + 2].x = clipped_trimmed_xmax; + list.vertices[vtx_offs + 2].y = clipped_trimmed_ymax; + list.vertices[vtx_offs + 2].u = clipped_umax; + list.vertices[vtx_offs + 2].v = clipped_vmax; + list.vertices[vtx_offs + 3].x = clipped_trimmed_xmin; + list.vertices[vtx_offs + 3].y = clipped_trimmed_ymax; + list.vertices[vtx_offs + 3].u = clipped_umin; + list.vertices[vtx_offs + 3].v = clipped_vmax; +} + +void TwodeePass::prepass(Rhi& rhi) +{ + if (!ctx_ || !data_) + { + return; + } + + if (data_->pipelines.size() == 0) + { + TwodeePipelineKey modulate_tris = {Draw2dBlend::kModulate, false}; + TwodeePipelineKey additive_tris = {Draw2dBlend::kAdditive, false}; + TwodeePipelineKey subtractive_tris = {Draw2dBlend::kSubtractive, false}; + TwodeePipelineKey revsubtractive_tris = {Draw2dBlend::kReverseSubtractive, false}; + TwodeePipelineKey invertdest_tris = {Draw2dBlend::kInvertDest, false}; + TwodeePipelineKey modulate_lines = {Draw2dBlend::kModulate, true}; + TwodeePipelineKey additive_lines = {Draw2dBlend::kAdditive, true}; + TwodeePipelineKey subtractive_lines = {Draw2dBlend::kSubtractive, true}; + TwodeePipelineKey revsubtractive_lines = {Draw2dBlend::kReverseSubtractive, true}; + TwodeePipelineKey invertdest_lines = {Draw2dBlend::kInvertDest, true}; + data_->pipelines.insert({modulate_tris, rhi.create_pipeline(make_pipeline_desc(modulate_tris))}); + data_->pipelines.insert({additive_tris, rhi.create_pipeline(make_pipeline_desc(additive_tris))}); + data_->pipelines.insert({subtractive_tris, rhi.create_pipeline(make_pipeline_desc(subtractive_tris))}); + data_->pipelines.insert({revsubtractive_tris, rhi.create_pipeline(make_pipeline_desc(revsubtractive_tris))}); + data_->pipelines.insert({invertdest_tris, rhi.create_pipeline(make_pipeline_desc(invertdest_tris))}); + data_->pipelines.insert({modulate_lines, rhi.create_pipeline(make_pipeline_desc(modulate_lines))}); + data_->pipelines.insert({additive_lines, rhi.create_pipeline(make_pipeline_desc(additive_lines))}); + data_->pipelines.insert({subtractive_lines, rhi.create_pipeline(make_pipeline_desc(subtractive_lines))}); + data_->pipelines.insert({revsubtractive_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); + data_->pipelines.insert({invertdest_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); + } + + if (!data_->default_tex) + { + data_->default_tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, 2, 1}); + data_->upload_default_tex = true; + } + if (!data_->palette_tex) + { + data_->palette_tex = rhi.create_texture({TextureFormat::kRGBA, 256, 1}); + } + if (!data_->default_colormap_tex) + { + data_->default_colormap_tex = rhi.create_texture({TextureFormat::kLuminance, 256, 1}); + data_->upload_default_tex = true; + } + if (!render_pass_) + { + render_pass_ = rhi.create_render_pass( + {std::nullopt, PixelFormat::kRGBA8, AttachmentLoadOp::kLoad, AttachmentStoreOp::kStore} + ); + } + + // Check for patches that are being freed after this frame. Those patches must be present in the atlases for this + // frame, but all atlases need to be cleared and rebuilt on next call to prepass. + // This is based on the assumption that patches are very rarely freed during runtime; occasionally repacking the + // atlases to free up space from patches that will never be referenced again is acceptable. + if (rebuild_atlases_) + { + for (auto& atlas : data_->patch_atlases) + { + rhi.destroy_texture(atlas.tex); + } + data_->patch_atlases.clear(); + data_->patch_lookup.clear(); + rebuild_atlases_ = false; + } + + if (data_->patch_atlases.size() > 2) + { + // Rebuild the atlases next frame because we have too many patches in the atlas cache. + rebuild_atlases_ = true; + } + + // Stage 1 - command list patch detection + std::unordered_set found_patches; + std::unordered_set found_colormaps; + for (const auto& list : *ctx_) + { + for (const auto& cmd : list.cmds) + { + auto visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) + { + if (cmd.patch != nullptr) + { + found_patches.insert(cmd.patch); + } + if (cmd.colormap != nullptr) + { + found_colormaps.insert(cmd.colormap); + } + }, + [&](const Draw2dVertices& cmd) {}}; + std::visit(visitor, cmd); + } + } + + std::unordered_set patch_cache_hits; + std::unordered_set patch_cache_misses; + for (auto patch : found_patches) + { + if (data_->patch_lookup.find(patch) != data_->patch_lookup.end()) + { + patch_cache_hits.insert(patch); + } + else + { + patch_cache_misses.insert(patch); + } + } + + for (auto colormap : found_colormaps) + { + if (data_->colormaps.find(colormap) == data_->colormaps.end()) + { + Handle colormap_tex = rhi.create_texture({TextureFormat::kLuminance, 256, 1}); + data_->colormaps.insert({colormap, colormap_tex}); + } + + data_->colormaps_to_upload.push_back(colormap); + } + + // Stage 2 - pack rects into atlases + std::vector patches_to_pack(patch_cache_misses.begin(), patch_cache_misses.end()); + pack_patches(rhi, *data_, patches_to_pack); + // We now know what patches need to be uploaded. + + size_t list_index = 0; + for (auto& list : *ctx_) + { + Handle vbo; + uint32_t vertex_data_size = tcb::as_bytes(tcb::span(list.vertices)).size(); + uint32_t needed_vbo_size = std::max( + kVboInitSize, + ((static_cast(vertex_data_size) + kVboInitSize - 1) / kVboInitSize) * kVboInitSize + ); + + // Get the existing buffer objects. Recreate them if they don't exist, or needs to be bigger. + + if (list_index >= vbos_.size()) + { + vbo = rhi.create_buffer({needed_vbo_size, BufferType::kVertexBuffer, BufferUsage::kDynamic}); + vbos_.push_back({vbo, needed_vbo_size}); + } + else + { + uint32_t existing_size = std::get<1>(vbos_[list_index]); + if (needed_vbo_size > existing_size) + { + rhi.destroy_buffer(std::get<0>(vbos_[list_index])); + vbo = rhi.create_buffer({needed_vbo_size, BufferType::kVertexBuffer, BufferUsage::kDynamic}); + vbos_[list_index] = {vbo, needed_vbo_size}; + } + vbo = std::get<0>(vbos_[list_index]); + } + + Handle ibo; + uint32_t index_data_size = tcb::as_bytes(tcb::span(list.indices)).size(); + uint32_t needed_ibo_size = std::max( + kIboInitSize, + ((static_cast(index_data_size) + kIboInitSize - 1) / kIboInitSize) * kIboInitSize + ); + + if (list_index >= ibos_.size()) + { + ibo = rhi.create_buffer({needed_ibo_size, BufferType::kIndexBuffer, BufferUsage::kDynamic}); + ibos_.push_back({ibo, needed_ibo_size}); + } + else + { + uint32_t existing_size = std::get<1>(ibos_[list_index]); + if (needed_ibo_size > existing_size) + { + rhi.destroy_buffer(std::get<0>(ibos_[list_index])); + ibo = rhi.create_buffer({needed_ibo_size, BufferType::kIndexBuffer, BufferUsage::kDynamic}); + ibos_[list_index] = {ibo, needed_ibo_size}; + } + ibo = std::get<0>(ibos_[list_index]); + } + + // Create a merged command list + MergedTwodeeCommandList merged_list; + merged_list.vbo = vbo; + merged_list.vbo_size = needed_vbo_size; + merged_list.ibo = ibo; + merged_list.ibo_size = needed_ibo_size; + + MergedTwodeeCommand new_cmd; + new_cmd.index_offset = 0; + new_cmd.elements = 0; + new_cmd.colormap = nullptr; + // safety: a command list is required to have at least 1 command + new_cmd.pipeline_key = pipeline_key_for_cmd(list.cmds[0]); + merged_list.cmds.push_back(std::move(new_cmd)); + + for (auto& cmd : list.cmds) + { + auto& merged_cmd = *merged_list.cmds.rbegin(); + bool new_cmd_needed = false; + TwodeePipelineKey pk = pipeline_key_for_cmd(cmd); + new_cmd_needed = new_cmd_needed || (pk != merged_cmd.pipeline_key); + + // We need to split the merged commands based on the kind of texture + // Patches are converted to atlas texture indexes, which we've just packed the patch rects for + // Flats are uploaded as individual textures. + // TODO actually implement flat drawing + auto tex_visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) + { + if (cmd.patch == nullptr) + { + new_cmd_needed = new_cmd_needed || (merged_cmd.texture != std::nullopt); + } + else + { + size_t atlas_index = data_->patch_lookup[cmd.patch]; + typeof(merged_cmd.texture) atlas_index_texture = atlas_index; + new_cmd_needed = new_cmd_needed || (merged_cmd.texture != atlas_index_texture); + } + + new_cmd_needed = new_cmd_needed || (merged_cmd.colormap != cmd.colormap); + }, + [&](const Draw2dVertices& cmd) + { + if (cmd.flat_lump == LUMPERROR) + { + new_cmd_needed |= (merged_cmd.texture != std::nullopt); + } + else + { + typeof(merged_cmd.texture) flat_tex = MergedTwodeeCommandFlatTexture {cmd.flat_lump}; + new_cmd_needed |= (merged_cmd.texture != flat_tex); + } + + new_cmd_needed = new_cmd_needed || (merged_cmd.colormap != nullptr); + }}; + std::visit(tex_visitor, cmd); + + if (new_cmd_needed) + { + MergedTwodeeCommand the_new_one; + the_new_one.index_offset = merged_cmd.index_offset + merged_cmd.elements; + + // Map to the merged version of the texture variant. Yay...! + auto tex_visitor_again = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) + { + if (cmd.patch != nullptr) + { + the_new_one.texture = data_->patch_lookup[cmd.patch]; + } + else + { + the_new_one.texture = std::nullopt; + } + the_new_one.colormap = cmd.colormap; + }, + [&](const Draw2dVertices& cmd) + { + if (cmd.flat_lump != LUMPERROR) + { + flat_manager_->find_or_create_indexed(rhi, cmd.flat_lump); + typeof(the_new_one.texture) t = MergedTwodeeCommandFlatTexture {cmd.flat_lump}; + the_new_one.texture = t; + } + else + { + the_new_one.texture = std::nullopt; + } + + the_new_one.colormap = nullptr; + }}; + std::visit(tex_visitor_again, cmd); + the_new_one.pipeline_key = pipeline_key_for_cmd(cmd); + merged_list.cmds.push_back(std::move(the_new_one)); + } + + // There may or may not be a new current command; update its element count + auto& new_merged_cmd = *merged_list.cmds.rbegin(); + // We know for sure that all commands in a command list have a contiguous range of elements in the IBO + // So we can draw them in batch if the pipeline key and textures match + new_merged_cmd.elements += hwr2::elements(cmd); + + // Perform coordinate transformations + { + auto vtx_transform_visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) { rewrite_patch_quad_vertices(list, cmd, data_.get()); }, + [&](const Draw2dVertices& cmd) {}}; + std::visit(vtx_transform_visitor, cmd); + } + } + + cmd_lists_.push_back(std::move(merged_list)); + + list_index++; + } +} + +void TwodeePass::transfer(Rhi& rhi, Handle ctx) +{ + if (!ctx_ || !data_) + { + return; + } + + if (data_->upload_default_tex) + { + std::array data = {0, 255, 0, 255}; + rhi.update_texture(ctx, data_->default_tex, {0, 0, 2, 1}, PixelFormat::kRG8, tcb::as_bytes(tcb::span(data))); + + std::array colormap_data; + for (size_t i = 0; i < 256; i++) + { + colormap_data[i] = i; + } + rhi.update_texture( + ctx, + data_->default_colormap_tex, + {0, 0, 256, 1}, + PixelFormat::kR8, + tcb::as_bytes(tcb::span(colormap_data)) + ); + + data_->upload_default_tex = false; + } + + { + // TODO share palette tex with software pass + // Unfortunately, pMasterPalette must be swizzled to get a linear layout. + // Maybe some adjustments to palette storage can make this a straight upload. + std::array palette_32; + for (size_t i = 0; i < 256; i++) + { + palette_32[i] = pMasterPalette[i].s; + } + rhi.update_texture( + ctx, + data_->palette_tex, + {0, 0, 256, 1}, + rhi::PixelFormat::kRGBA8, + tcb::as_bytes(tcb::span(palette_32)) + ); + } + + for (auto colormap : data_->colormaps_to_upload) + { + rhi.update_texture( + ctx, + data_->colormaps[colormap], + {0, 0, 256, 1}, + rhi::PixelFormat::kR8, + tcb::as_bytes(tcb::span(colormap, 256)) + ); + } + data_->colormaps_to_upload.clear(); + + // Convert patches to RG8 textures and upload to atlas pages + std::vector patch_data; + for (const patch_t* patch_to_upload : data_->patches_to_upload) + { + Atlas& atlas = data_->patch_atlases[data_->patch_lookup[patch_to_upload]]; + AtlasEntry& entry = atlas.entries[patch_to_upload]; + + convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data); + + rhi.update_texture( + ctx, + atlas.tex, + {static_cast(entry.x), static_cast(entry.y), entry.w, entry.h}, + PixelFormat::kRG8, + tcb::as_bytes(tcb::span(patch_data)) + ); + } + data_->patches_to_upload.clear(); + + // Update the buffers for each list + auto ctx_list_itr = ctx_->begin(); + for (size_t i = 0; i < cmd_lists_.size() && ctx_list_itr != ctx_->end(); i++) + { + auto& merged_list = cmd_lists_[i]; + auto& orig_list = *ctx_list_itr; + + tcb::span vertex_data = tcb::as_bytes(tcb::span(orig_list.vertices)); + tcb::span index_data = tcb::as_bytes(tcb::span(orig_list.indices)); + rhi.update_buffer_contents(ctx, merged_list.vbo, 0, vertex_data); + rhi.update_buffer_contents(ctx, merged_list.ibo, 0, index_data); + + // Update the binding sets for each individual merged command + VertexAttributeBufferBinding vbos[] = {{0, merged_list.vbo}}; + for (auto& mcmd : merged_list.cmds) + { + TextureBinding tx[3]; + auto tex_visitor = srb2::Overload { + [&](size_t atlas_index) + { + Atlas& atlas = data_->patch_atlases[atlas_index]; + tx[0] = {SamplerName::kSampler0, atlas.tex}; + tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + }, + [&](const MergedTwodeeCommandFlatTexture& tex) + { + Handle th = flat_manager_->find_indexed(tex.lump); + SRB2_ASSERT(th != kNullHandle); + tx[0] = {SamplerName::kSampler0, th}; + tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + }}; + if (mcmd.texture) + { + std::visit(tex_visitor, *mcmd.texture); + } + else + { + tx[0] = {SamplerName::kSampler0, data_->default_tex}; + tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + } + + const uint8_t* colormap = mcmd.colormap; + Handle colormap_h = data_->default_colormap_tex; + if (colormap) + { + SRB2_ASSERT(data_->colormaps.find(colormap) != data_->colormaps.end()); + colormap_h = data_->colormaps[colormap]; + } + tx[2] = {SamplerName::kSampler2, colormap_h}; + mcmd.binding_set = + rhi.create_binding_set(ctx, data_->pipelines[mcmd.pipeline_key], {tcb::span(vbos), tcb::span(tx)}); + } + + ctx_list_itr++; + } + + // Uniform sets + std::array g1_uniforms = {{ + // Projection + std::array, 4> { + {{2.f / vid.width, 0.f, 0.f, 0.f}, + {0.f, -2.f / vid.height, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, + {-1.f, 1.f, 0.f, 1.f}}}, + }}; + std::array g2_uniforms = { + {// ModelView + std::array, 4> { + {{1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}}, + // Texcoord0 Transform + std::array, 3> {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}}, + // Sampler 0 Is Indexed Alpha (yes, it always is) + static_cast(1)}}; + us_1 = rhi.create_uniform_set(ctx, {tcb::span(g1_uniforms)}); + us_2 = rhi.create_uniform_set(ctx, {tcb::span(g2_uniforms)}); +} + +static constexpr const rhi::Color kClearColor = {0, 0, 0, 1}; + +void TwodeePass::graphics(Rhi& rhi, Handle ctx) +{ + if (!ctx_ || !data_) + { + return; + } + + if (output_) + { + rhi.begin_render_pass(ctx, {render_pass_, output_, std::nullopt, kClearColor}); + } + else + { + rhi.begin_default_render_pass(ctx, false); + } + + for (auto& list : cmd_lists_) + { + for (auto& cmd : list.cmds) + { + if (cmd.elements == 0) + { + // Don't do anything for 0-element commands + // This shouldn't happen, but, just in case... + continue; + } + SRB2_ASSERT(data_->pipelines.find(cmd.pipeline_key) != data_->pipelines.end()); + Handle pl = data_->pipelines[cmd.pipeline_key]; + rhi.bind_pipeline(ctx, pl); + if (output_) + { + rhi.set_viewport(ctx, {0, 0, output_width_, output_height_}); + } + rhi.bind_uniform_set(ctx, 0, us_1); + rhi.bind_uniform_set(ctx, 1, us_2); + rhi.bind_binding_set(ctx, cmd.binding_set); + rhi.bind_index_buffer(ctx, list.ibo); + rhi.draw_indexed(ctx, cmd.elements, cmd.index_offset); + } + } + rhi.end_render_pass(ctx); +} + +void TwodeePass::postpass(Rhi& rhi) +{ + if (!ctx_ || !data_) + { + return; + } + + cmd_lists_.clear(); +} diff --git a/src/hwr2/pass_twodee.hpp b/src/hwr2/pass_twodee.hpp new file mode 100644 index 000000000..da9c2b563 --- /dev/null +++ b/src/hwr2/pass_twodee.hpp @@ -0,0 +1,116 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_TWODEE_HPP__ +#define __SRB2_HWR2_PASS_TWODEE_HPP__ + +#include +#include +#include +#include +#include +#include + +#include "../cxxutil.hpp" +#include "pass.hpp" +#include "pass_resource_managers.hpp" +#include "twodee.hpp" + +namespace srb2::hwr2 +{ + +class TwodeePass; + +/// @brief Shared structures to allow multiple 2D instances to share the same atlases +struct TwodeePassData; + +/// @brief Hash map key for caching pipelines +struct TwodeePipelineKey +{ + Draw2dBlend blend; + bool lines; + + bool operator==(const TwodeePipelineKey& r) const noexcept { return !(blend != r.blend || lines != r.lines); } + bool operator!=(const TwodeePipelineKey& r) const noexcept { return !(*this == r); } +}; + +struct MergedTwodeeCommandFlatTexture +{ + lumpnum_t lump; + + bool operator==(const MergedTwodeeCommandFlatTexture& rhs) const noexcept { return lump == rhs.lump; } + bool operator!=(const MergedTwodeeCommandFlatTexture& rhs) const noexcept { return !(*this == rhs); } +}; + +struct MergedTwodeeCommand +{ + TwodeePipelineKey pipeline_key = {}; + rhi::Handle binding_set = {}; + std::optional> texture; + const uint8_t* colormap; + uint32_t index_offset = 0; + uint32_t elements = 0; +}; + +struct MergedTwodeeCommandList +{ + rhi::Handle vbo {}; + uint32_t vbo_size = 0; + rhi::Handle ibo {}; + uint32_t ibo_size = 0; + + std::vector cmds; +}; + +std::shared_ptr make_twodee_pass_data(); + +struct TwodeePass final : public Pass +{ + Twodee* ctx_ = nullptr; + std::variant, rhi::Handle> out_color_; + + std::shared_ptr data_; + std::shared_ptr flat_manager_; + rhi::Handle us_1; + rhi::Handle us_2; + std::vector cmd_lists_; + std::vector, std::size_t>> vbos_; + std::vector, std::size_t>> ibos_; + bool rebuild_atlases_ = false; + rhi::Handle render_pass_; + rhi::Handle output_; + uint32_t output_width_ = 0; + uint32_t output_height_ = 0; + + TwodeePass(); + virtual ~TwodeePass(); + + virtual void prepass(rhi::Rhi& rhi) override; + + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + + virtual void postpass(rhi::Rhi& rhi) override; +}; + +} // namespace srb2::hwr2 + +template <> +struct std::hash +{ + std::size_t operator()(const srb2::hwr2::TwodeePipelineKey& v) const + { + std::size_t hash = 0; + srb2::hash_combine(hash, v.blend, v.lines); + return hash; + } +}; + +#endif // __SRB2_HWR2_PASS_TWODEE_HPP__ diff --git a/src/hwr2/twodee.cpp b/src/hwr2/twodee.cpp new file mode 100644 index 000000000..8a15234fe --- /dev/null +++ b/src/hwr2/twodee.cpp @@ -0,0 +1,114 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "twodee.hpp" + +#include "../w_wad.h" + +using namespace srb2; +using namespace hwr2; + +Twodee::Twodee() = default; +Twodee::Twodee(const Twodee&) = default; +Twodee::Twodee(Twodee&&) noexcept = default; +Twodee& Twodee::operator=(const Twodee&) = default; + +// Will the default move prevent the vectors from losing their allocations? I guess it depends on the STL impl. +// It's probably worth optimizing around. +Twodee& Twodee::operator=(Twodee&&) noexcept = default; + +void Draw2dQuadBuilder::done() +{ + if (ctx_.lists_.size() == 0) + { + ctx_.lists_.push_back({}); + } + + if (ctx_.lists_.rbegin()->vertices.size() >= (Draw2dList::kMaxVertices - 4)) + { + // The current draw list has too many vertices to fit this command + ctx_.lists_.push_back({}); + } + + auto& list = *ctx_.lists_.rbegin(); + quad_.begin_element = list.vertices.size(); + quad_.begin_index = list.vertices.size(); + + list.vertices.push_back({quad_.xmin, quad_.ymin, 0.f, 0, 0, quad_.r, quad_.g, quad_.b, quad_.a}); + list.vertices.push_back({quad_.xmax, quad_.ymin, 0.f, 1, 0, quad_.r, quad_.g, quad_.b, quad_.a}); + list.vertices.push_back({quad_.xmax, quad_.ymax, 0.f, 1, 1, quad_.r, quad_.g, quad_.b, quad_.a}); + list.vertices.push_back({quad_.xmin, quad_.ymax, 0.f, 0, 1, quad_.r, quad_.g, quad_.b, quad_.a}); + + list.indices.push_back(quad_.begin_element + 0); + list.indices.push_back(quad_.begin_element + 1); + list.indices.push_back(quad_.begin_element + 2); + + list.indices.push_back(quad_.begin_element + 0); + list.indices.push_back(quad_.begin_element + 2); + list.indices.push_back(quad_.begin_element + 3); + + list.cmds.push_back(quad_); +} + +void Draw2dVerticesBuilder::done() +{ + if (ctx_.lists_.size() == 0) + { + ctx_.lists_.push_back({}); + } + + if (ctx_.lists_.rbegin()->vertices.size() >= (Draw2dList::kMaxVertices - 4)) + { + // The current draw list has too many vertices to fit this command + ctx_.lists_.push_back({}); + } + + auto& list = *ctx_.lists_.rbegin(); + tris_.begin_element = list.vertices.size(); + tris_.begin_index = list.indices.size(); + + if (verts_.empty()) + { + return; + } + + std::size_t i = 0; + for (auto& vert : verts_) + { + list.vertices.push_back({vert[0], vert[1], 0, vert[2], vert[3], r_, g_, b_, a_}); + list.indices.push_back(tris_.begin_element + i); + i++; + } + + list.cmds.push_back(tris_); +} + +Draw2dBlend srb2::hwr2::get_blend_mode(const Draw2dCmd& cmd) noexcept +{ + auto visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) { return cmd.blend; }, + [&](const Draw2dVertices& cmd) { return cmd.blend; }}; + return std::visit(visitor, cmd); +} + +bool srb2::hwr2::is_draw_lines(const Draw2dCmd& cmd) noexcept +{ + auto visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) { return false; }, + [&](const Draw2dVertices& cmd) { return cmd.lines; }}; + return std::visit(visitor, cmd); +} + +std::size_t srb2::hwr2::elements(const Draw2dCmd& cmd) noexcept +{ + auto visitor = srb2::Overload { + [&](const Draw2dPatchQuad& cmd) -> std::size_t { return 6; }, + [&](const Draw2dVertices& cmd) -> std::size_t { return cmd.elements; }}; + return std::visit(visitor, cmd); +} diff --git a/src/hwr2/twodee.hpp b/src/hwr2/twodee.hpp new file mode 100644 index 000000000..10cc8e6b1 --- /dev/null +++ b/src/hwr2/twodee.hpp @@ -0,0 +1,280 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_TWODEE_HPP__ +#define __SRB2_HWR2_TWODEE_HPP__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "../cxxutil.hpp" +#include "../doomtype.h" + +namespace srb2::hwr2 +{ + +struct TwodeeVertex +{ + float x; + float y; + float z; + float u; + float v; + float r; + float g; + float b; + float a; +}; + +enum class Draw2dBlend +{ + kModulate, + kAdditive, + kSubtractive, + kReverseSubtractive, + kInvertDest +}; + +struct Draw2dPatchQuad +{ + std::size_t begin_index = 0; + std::size_t begin_element = 0; + + // A null patch ptr means no patch is drawn + const patch_t* patch = nullptr; + const uint8_t* colormap = nullptr; + Draw2dBlend blend; + float r = 0.f; + float g = 0.f; + float b = 0.f; + float a = 0.f; + + // Size fields are made available to let the consumer modify the vertex data for optimization + float xmin = 0.f; + float ymin = 0.f; + float xmax = 0.f; + float ymax = 0.f; + float clip_xmin = 0.f; + float clip_xmax = 0.f; + float clip_ymin = 0.f; + float clip_ymax = 0.f; + bool clip = false; + bool flip = false; + bool vflip = false; +}; + +struct Draw2dVertices +{ + std::size_t begin_index = 0; + std::size_t begin_element = 0; + std::size_t elements = 0; + Draw2dBlend blend = Draw2dBlend::kModulate; + lumpnum_t flat_lump = UINT32_MAX; // LUMPERROR but not loading w_wad.h from this header + bool lines = false; +}; + +using Draw2dCmd = std::variant; + +Draw2dBlend get_blend_mode(const Draw2dCmd& cmd) noexcept; +bool is_draw_lines(const Draw2dCmd& cmd) noexcept; +std::size_t elements(const Draw2dCmd& cmd) noexcept; + +struct Draw2dList +{ + std::vector vertices; + std::vector indices; + std::vector cmds; + + static constexpr const std::size_t kMaxVertices = 65536; +}; + +class Draw2dQuadBuilder; +class Draw2dVerticesBuilder; + +/// @brief Buffered 2D drawing context +class Twodee +{ + std::vector lists_; + std::vector current_verts_; + std::vector current_indices_; + + friend class Draw2dQuadBuilder; + friend class Draw2dVerticesBuilder; + +public: + Twodee(); + Twodee(const Twodee&); + Twodee(Twodee&&) noexcept; + + Twodee& operator=(const Twodee&); + Twodee& operator=(Twodee&&) noexcept; + + Draw2dQuadBuilder begin_quad() noexcept; + Draw2dVerticesBuilder begin_verts() noexcept; + + typename std::vector::iterator begin() noexcept { return lists_.begin(); } + typename std::vector::iterator end() noexcept { return lists_.end(); } + typename std::vector::const_iterator begin() const noexcept { return lists_.cbegin(); } + typename std::vector::const_iterator end() const noexcept { return lists_.cend(); } + typename std::vector::const_iterator cbegin() const noexcept { return lists_.cbegin(); } + typename std::vector::const_iterator cend() const noexcept { return lists_.cend(); } +}; + +class Draw2dQuadBuilder +{ + Draw2dPatchQuad quad_; + Twodee& ctx_; + + Draw2dQuadBuilder(Twodee& ctx) : quad_ {}, ctx_ {ctx} {} + + friend class Twodee; + +public: + Draw2dQuadBuilder(const Draw2dQuadBuilder&) = delete; + Draw2dQuadBuilder(Draw2dQuadBuilder&&) = default; + Draw2dQuadBuilder& operator=(const Draw2dQuadBuilder&) = delete; + Draw2dQuadBuilder& operator=(Draw2dQuadBuilder&&) = default; + + Draw2dQuadBuilder& rect(float x, float y, float w, float h) + { + quad_.xmin = x; + quad_.xmax = x + w; + quad_.ymin = y; + quad_.ymax = y + h; + return *this; + } + + Draw2dQuadBuilder& flip(bool flip) + { + quad_.flip = flip; + return *this; + } + + Draw2dQuadBuilder& vflip(bool vflip) + { + quad_.vflip = vflip; + return *this; + } + + Draw2dQuadBuilder& clip(float xmin, float ymin, float xmax, float ymax) + { + quad_.clip_xmin = xmin; + quad_.clip_ymin = ymin; + quad_.clip_xmax = xmax; + quad_.clip_ymax = ymax; + quad_.clip = true; + return *this; + } + + Draw2dQuadBuilder& color(float r, float g, float b, float a) + { + quad_.r = r; + quad_.g = g; + quad_.b = b; + quad_.a = a; + return *this; + } + + Draw2dQuadBuilder& patch(const patch_t* patch) + { + quad_.patch = patch; + return *this; + } + + Draw2dQuadBuilder& blend(Draw2dBlend blend) + { + quad_.blend = blend; + return *this; + } + + Draw2dQuadBuilder& colormap(const uint8_t* colormap) + { + quad_.colormap = colormap; + return *this; + } + + void done(); +}; + +class Draw2dVerticesBuilder +{ + Draw2dVertices tris_; + Twodee& ctx_; + std::vector> verts_; + float r_ = 1.f; + float g_ = 1.f; + float b_ = 1.f; + float a_ = 1.f; + + Draw2dVerticesBuilder(Twodee& ctx) : tris_ {}, ctx_ {ctx} {} + + friend class Twodee; + +public: + Draw2dVerticesBuilder(const Draw2dVerticesBuilder&) = delete; + Draw2dVerticesBuilder(Draw2dVerticesBuilder&&) = default; + Draw2dVerticesBuilder& operator=(const Draw2dVerticesBuilder&) = delete; + Draw2dVerticesBuilder& operator=(Draw2dVerticesBuilder&&) = default; + + Draw2dVerticesBuilder& vert(float x, float y, float u = 0, float v = 0) + { + verts_.push_back({x, y, u, v}); + tris_.elements += 1; + return *this; + } + + Draw2dVerticesBuilder& color(float r, float g, float b, float a) + { + r_ = r; + g_ = g; + b_ = b; + a_ = a; + return *this; + } + + Draw2dVerticesBuilder& blend(Draw2dBlend blend) + { + tris_.blend = blend; + return *this; + } + + Draw2dVerticesBuilder& lines(bool lines) + { + tris_.lines = lines; + return *this; + } + + Draw2dVerticesBuilder& flat(lumpnum_t lump) + { + tris_.flat_lump = lump; + return *this; + } + + void done(); +}; + +inline Draw2dQuadBuilder Twodee::begin_quad() noexcept +{ + return Draw2dQuadBuilder(*this); +} + +inline Draw2dVerticesBuilder Twodee::begin_verts() noexcept +{ + return Draw2dVerticesBuilder(*this); +} + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_TWODEE_HPP__ diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 14f5e31ae..040c477c0 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "i_video.h" #include @@ -7,24 +16,30 @@ #include #include "cxxutil.hpp" +#include "f_finale.h" +#include "hwr2/pass_blit_rect.hpp" #include "hwr2/pass_imgui.hpp" +#include "hwr2/pass_manager.hpp" +#include "hwr2/pass_postprocess.hpp" +#include "hwr2/pass_resource_managers.hpp" #include "hwr2/pass_software.hpp" +#include "hwr2/pass_twodee.hpp" +#include "hwr2/twodee.hpp" #include "v_video.h" // KILL THIS WHEN WE KILL OLD OGL SUPPORT PLEASE +#include "d_netcmd.h" // kill +#include "discord.h" // kill +#include "doomstat.h" // kill +#include "s_sound.h" // kill #include "sdl/ogl_sdl.h" #include "st_stuff.h" // kill -#include "d_netcmd.h" // kill -#include "doomstat.h" // kill -#include "s_sound.h" // kill -#include "discord.h" // kill using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -static SoftwareBlitPass g_sw_pass; -static ImguiPass g_imgui_pass; +static std::shared_ptr g_passmanager; Handle srb2::sys::g_current_rhi = kNullHandle; @@ -48,8 +63,7 @@ static void finish_legacy_ogl_update() if (cv_ticrate.value) SCR_DisplayTicRate(); - if (cv_showping.value && netgame && - ( consoleplayer != serverplayer || ! server_lagless )) + if (cv_showping.value && netgame && (consoleplayer != serverplayer || !server_lagless)) { if (server_lagless) { @@ -58,11 +72,8 @@ static void finish_legacy_ogl_update() } else { - for ( - player = 1; - player < MAXPLAYERS; - player++ - ){ + for (player = 1; player < MAXPLAYERS; player++) + { if (D_IsPlayerHumanAndGaming(player)) { SCR_DisplayLocalPing(); @@ -91,6 +102,154 @@ static void finish_legacy_ogl_update() } #endif +static std::shared_ptr build_pass_manager() +{ + std::shared_ptr manager = std::make_shared(); + + std::shared_ptr framebuffer_manager = std::make_shared(); + std::shared_ptr palette_manager = std::make_shared(); + std::shared_ptr flat_texture_manager = std::make_shared(); + + std::shared_ptr software_pass = std::make_shared(); + std::shared_ptr blit_sw_pass = std::make_shared(palette_manager, true); + std::shared_ptr twodee = std::make_shared(); + twodee->flat_manager_ = flat_texture_manager; + twodee->data_ = make_twodee_pass_data(); + twodee->ctx_ = &g_2d; + std::shared_ptr pp_simple_blit_pass = std::make_shared(false); + std::shared_ptr pp_wipe_pass = std::make_shared(); + std::shared_ptr imgui_pass = std::make_shared(); + std::shared_ptr final_composite_pass = std::make_shared(true); + + manager->insert("framebuffer_manager", framebuffer_manager); + manager->insert("palette_manager", palette_manager); + manager->insert("flat_texture_manager", flat_texture_manager); + + manager->insert( + "3d_prepare", + [framebuffer_manager](PassManager& mgr, Rhi&) + { + const bool sw_enabled = rendermode == render_soft; + + mgr.set_pass_enabled("software", !g_wipeskiprender && sw_enabled); + mgr.set_pass_enabled("blit_sw_prepare", !g_wipeskiprender && sw_enabled); + mgr.set_pass_enabled("blit_sw", !g_wipeskiprender && sw_enabled); + }, + [framebuffer_manager](PassManager&, Rhi&) + { + if (!WipeInAction) + { + framebuffer_manager->swap_main(); + } + } + ); + manager->insert("software", software_pass); + manager->insert( + "blit_sw_prepare", + [blit_sw_pass, software_pass, framebuffer_manager](PassManager&, Rhi&) + { + blit_sw_pass->set_texture(software_pass->screen_texture(), vid.width, vid.height); + blit_sw_pass->set_output(framebuffer_manager->current_main_color(), vid.width, vid.height, false, false); + } + ); + manager->insert("blit_sw", blit_sw_pass); + + manager->insert( + "2d_prepare", + [twodee, framebuffer_manager](PassManager& mgr, Rhi&) + { + twodee->output_ = framebuffer_manager->current_main_color(); + twodee->output_width_ = vid.width; + twodee->output_height_ = vid.height; + } + ); + manager->insert("2d", twodee); + + manager->insert( + "pp_final_prepare", + [](PassManager& mgr, Rhi&) + { + mgr.set_pass_enabled("pp_final_wipe_prepare", WipeInAction); + mgr.set_pass_enabled("pp_final_wipe", WipeInAction); + mgr.set_pass_enabled("pp_final_wipe_flip", WipeInAction); + } + ); + manager->insert( + "pp_final_simple_blit_prepare", + [pp_simple_blit_pass, framebuffer_manager](PassManager&, Rhi&) + { + Handle color = framebuffer_manager->current_main_color(); + if (WipeInAction && !g_wipereverse) + { + // Non-reverse wipes are "fade-outs" from the previous frame. + color = framebuffer_manager->previous_main_color(); + } + pp_simple_blit_pass->set_texture(color, vid.width, vid.height); + pp_simple_blit_pass + ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true); + } + ); + manager->insert("pp_final_simple_blit", pp_simple_blit_pass); + manager->insert( + "pp_final_simple_blit_flip", + [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } + ); + manager->insert( + "pp_final_wipe_prepare", + [pp_wipe_pass, framebuffer_manager](PassManager&, Rhi&) + { + pp_wipe_pass->set_source(framebuffer_manager->previous_post_color(), vid.width, vid.height); + pp_wipe_pass->set_end(framebuffer_manager->current_main_color()); + pp_wipe_pass->set_target(framebuffer_manager->current_post_color(), vid.width, vid.height); + } + ); + manager->insert("pp_final_wipe", pp_wipe_pass); + manager->insert( + "pp_final_wipe_flip", + [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } + ); + + manager->insert( + "final_composite_prepare", + [final_composite_pass, framebuffer_manager](PassManager&, Rhi&) + { + final_composite_pass->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height); + final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, true); + } + ); + manager->insert("final_composite", final_composite_pass); + + manager->insert("imgui", imgui_pass); + + manager->insert( + "present", + [](PassManager&, Rhi& rhi) {}, + [framebuffer_manager](PassManager&, Rhi& rhi) + { + rhi.present(); + rhi.finish(); + framebuffer_manager->reset_post(); + + // TODO fix this: it's an ugly hack to work around issues with wipes + // Why this works: + // - Menus run F_RunWipe which is an inner update loop calling I_FinishUpdate, with this global set + // - After exiting F_RunWipe, g_2d should normally be cleared by I_FinishUpdate + // - Unfortunately, the menu has already run all its draw calls when exiting F_RunWipe + // - That causes a single-frame flash of no 2d content, which is an epilepsy risk. + // - By not clearing the 2d context, we are redrawing 2d every frame of the wipe + // - This "works" because we draw 2d to the normal color buffer, not the postprocessed screen. + // - It does result in the FPS counter being mangled during the wipe though. + // - To fix the issues around wipes, wipes need to be a "sub" game state, and eliminate the inner tic loops. + if (!WipeInAction) + { + g_2d = Twodee(); + } + } + ); + + return manager; +} + void I_FinishUpdate(void) { if (rendermode == render_none) @@ -112,11 +271,9 @@ void I_FinishUpdate(void) io.DisplaySize.y = vid.realheight; ImGui::NewFrame(); - if (rhi_changed()) + if (rhi_changed() || !g_passmanager) { - // reinitialize passes - g_sw_pass = SoftwareBlitPass(); - g_imgui_pass = ImguiPass(); + g_passmanager = build_pass_manager(); } rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); @@ -127,48 +284,5 @@ void I_FinishUpdate(void) return; } - // Prepare phase - if (rendermode == render_soft) - { - g_sw_pass.prepass(*rhi); - } - g_imgui_pass.prepass(*rhi); - - // Transfer phase - Handle tc; - tc = rhi->begin_transfer(); - - if (rendermode == render_soft) - { - g_sw_pass.transfer(*rhi, tc); - } - g_imgui_pass.transfer(*rhi, tc); - - rhi->end_transfer(tc); - - // Graphics phase - Handle gc; - gc = rhi->begin_graphics(); - - // Standard drawing passes... - if (rendermode == render_soft) - { - g_sw_pass.graphics(*rhi, gc); - } - g_imgui_pass.graphics(*rhi, gc); - - rhi->end_graphics(gc); - - // Postpass phase - if (rendermode == render_soft) - { - g_sw_pass.postpass(*rhi); - } - g_imgui_pass.postpass(*rhi); - - // Present - - rhi->present(); - - rhi->finish(); + g_passmanager->render(*rhi); } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b71179ede..a78423721 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -517,10 +517,7 @@ void M_Drawer(void) } else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) { - if (rendermode == render_opengl) // OGL can't handle what SW is doing so let's fake it; - V_DrawFadeScreen(122, 3); // palette index aproximation... - else // Software can keep its unique fade - V_DrawCustomFadeScreen("FADEMAP0", 4); // now that's more readable with a faded background (yeah like Quake...) + V_DrawFadeScreen(122, 3); } if (currentMenu->drawroutine) @@ -4802,7 +4799,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) unlockable_t *ref = NULL; UINT8 *colormap = NULL; UINT16 specialmap = NEXTMAP_INVALID; - + if (challengesmenu.currentunlock >= MAXUNLOCKABLES) { return; diff --git a/src/r_patch.cpp b/src/r_patch.cpp index a27e8035c..443e940a3 100644 --- a/src/r_patch.cpp +++ b/src/r_patch.cpp @@ -103,6 +103,7 @@ void Patch_Free(patch_t *patch) { if (!patch || patch == missingpat) return; + Patch_FreeData(patch); Z_Free(patch); } diff --git a/src/rhi/gl3_core/gl3_core_rhi.cpp b/src/rhi/gl3_core/gl3_core_rhi.cpp index 7b9500594..f98d25d0b 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.cpp +++ b/src/rhi/gl3_core/gl3_core_rhi.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "gl3_core_rhi.hpp" #include @@ -13,7 +22,7 @@ using namespace srb2; using namespace rhi; -#if 1 +#ifndef NDEBUG #define GL_ASSERT \ { \ GLenum __err = gl_->GetError(); \ @@ -56,6 +65,11 @@ constexpr std::tuple map_pixel_data_format(rhi::PixelFor type = GL_UNSIGNED_BYTE; size = 1; break; + case rhi::PixelFormat::kRG8: + layout = GL_RG; + type = GL_UNSIGNED_BYTE; + size = 2; + break; case rhi::PixelFormat::kRGBA8: layout = GL_RGBA; type = GL_UNSIGNED_BYTE; @@ -77,6 +91,27 @@ constexpr GLenum map_texture_format(rhi::TextureFormat format) return GL_RGB; case rhi::TextureFormat::kLuminance: return GL_RED; + case rhi::TextureFormat::kLuminanceAlpha: + return GL_RG; + default: + return GL_ZERO; + } +} + +constexpr GLenum map_internal_texture_format(rhi::TextureFormat format) +{ + switch (format) + { + case rhi::TextureFormat::kRGBA: + return GL_RGBA8; + case rhi::TextureFormat::kRGB: + return GL_RGB8; + case rhi::TextureFormat::kLuminance: + return GL_R8; + case rhi::TextureFormat::kLuminanceAlpha: + return GL_RG8; + case rhi::TextureFormat::kDepth: + return GL_DEPTH_COMPONENT24; default: return GL_ZERO; } @@ -286,6 +321,27 @@ constexpr const char* map_uniform_attribute_symbol_name(rhi::UniformName name) return "u_projection"; case rhi::UniformName::kTexCoord0Transform: return "u_texcoord0_transform"; + case rhi::UniformName::kSampler0IsIndexedAlpha: + return "u_sampler0_is_indexed_alpha"; + default: + return nullptr; + } +} + +constexpr const char* map_uniform_enable_define(rhi::UniformName name) +{ + switch (name) + { + case rhi::UniformName::kTime: + return "ENABLE_U_TIME"; + case rhi::UniformName::kProjection: + return "ENABLE_U_PROJECTION"; + case rhi::UniformName::kModelView: + return "ENABLE_U_MODELVIEW"; + case rhi::UniformName::kTexCoord0Transform: + return "ENABLE_U_TEXCOORD0_TRANSFORM"; + case rhi::UniformName::kSampler0IsIndexedAlpha: + return "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA"; default: return nullptr; } @@ -308,6 +364,23 @@ constexpr const char* map_sampler_symbol_name(rhi::SamplerName name) } } +constexpr const char* map_sampler_enable_define(rhi::SamplerName name) +{ + switch (name) + { + case rhi::SamplerName::kSampler0: + return "ENABLE_S_SAMPLER0"; + case rhi::SamplerName::kSampler1: + return "ENABLE_S_SAMPLER1"; + case rhi::SamplerName::kSampler2: + return "ENABLE_S_SAMPLER2"; + case rhi::SamplerName::kSampler3: + return "ENABLE_S_SAMPLER3"; + default: + return nullptr; + } +} + constexpr GLenum map_vertex_attribute_format(rhi::VertexAttributeFormat format) { switch (format) @@ -423,8 +496,13 @@ rhi::Handle GlCoreRhi::create_texture(const rhi::TextureDesc& desc { SRB2_ASSERT(graphics_context_active_ == false); - GLenum internal_format = map_texture_format(desc.format); + GLenum internal_format = map_internal_texture_format(desc.format); SRB2_ASSERT(internal_format != GL_ZERO); + GLenum format = GL_RGBA; + if (desc.format == TextureFormat::kDepth) + { + format = GL_DEPTH_COMPONENT; + } GLuint name = 0; gl_->GenTextures(1, &name); @@ -439,7 +517,7 @@ rhi::Handle GlCoreRhi::create_texture(const rhi::TextureDesc& desc GL_ASSERT gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); GL_ASSERT - gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, format, GL_UNSIGNED_BYTE, nullptr); GL_ASSERT GlCoreTexture texture; @@ -478,13 +556,19 @@ void GlCoreRhi::update_texture( SRB2_ASSERT(texture_slab_.is_valid(texture) == true); auto& t = texture_slab_[texture]; + // Each row of pixels must be on the unpack alignment boundary. + // This alignment is not user changeable until OpenGL 4. + constexpr const int32_t kUnpackAlignment = 4; + GLenum format = GL_RGBA; GLenum type = GL_UNSIGNED_BYTE; GLuint size = 0; std::tie(format, type, size) = map_pixel_data_format(data_format); SRB2_ASSERT(format != GL_ZERO && type != GL_ZERO); SRB2_ASSERT(map_texture_format(t.desc.format) == format); - SRB2_ASSERT(region.w * region.h * size == data.size_bytes()); + + int32_t expected_row_span = (((size * region.w) + kUnpackAlignment - 1) / kUnpackAlignment) * kUnpackAlignment; + SRB2_ASSERT(expected_row_span * region.h == data.size_bytes()); SRB2_ASSERT(region.x + region.w <= t.desc.width && region.y + region.h <= t.desc.height); gl_->ActiveTexture(GL_TEXTURE0); @@ -740,14 +824,79 @@ rhi::Handle GlCoreRhi::create_pipeline(const PipelineDesc& desc) } } } + for (auto& uniform_group : desc.uniform_input.enabled_uniforms) + { + for (auto& uniform : uniform_group) + { + for (auto const& req_uni_group : reqs.uniforms.uniform_groups) + { + for (auto const& req_uni : req_uni_group) + { + if (req_uni.name == uniform && !req_uni.required) + { + vert_src_processed.append("#define "); + vert_src_processed.append(map_uniform_enable_define(uniform)); + vert_src_processed.append("\n"); + } + } + } + } + } + } + string_i = new_i + 1; + } while (string_i != std::string::npos); + + std::string frag_src_processed; + string_i = 0; + do + { + std::string::size_type new_i = frag_src.find('\n', string_i); + if (new_i == std::string::npos) + { + break; + } + std::string_view line_view(frag_src.c_str() + string_i, new_i - string_i + 1); + frag_src_processed.append(line_view); + if (line_view.rfind("#version ", 0) == 0) + { + for (auto& sampler : desc.sampler_input.enabled_samplers) + { + for (auto const& require_sampler : reqs.samplers.samplers) + { + if (sampler == require_sampler.name && !require_sampler.required) + { + frag_src_processed.append("#define "); + frag_src_processed.append(map_sampler_enable_define(sampler)); + frag_src_processed.append("\n"); + } + } + } + for (auto& uniform_group : desc.uniform_input.enabled_uniforms) + { + for (auto& uniform : uniform_group) + { + for (auto const& req_uni_group : reqs.uniforms.uniform_groups) + { + for (auto const& req_uni : req_uni_group) + { + if (req_uni.name == uniform && !req_uni.required) + { + frag_src_processed.append("#define "); + frag_src_processed.append(map_uniform_enable_define(uniform)); + frag_src_processed.append("\n"); + } + } + } + } + } } string_i = new_i + 1; } while (string_i != std::string::npos); const char* vert_src_arr[1] = {vert_src_processed.c_str()}; const GLint vert_src_arr_lens[1] = {static_cast(vert_src_processed.size())}; - const char* frag_src_arr[1] = {frag_src.c_str()}; - const GLint frag_src_arr_lens[1] = {static_cast(frag_src.size())}; + const char* frag_src_arr[1] = {frag_src_processed.c_str()}; + const GLint frag_src_arr_lens[1] = {static_cast(frag_src_processed.size())}; vertex = gl_->CreateShader(GL_VERTEX_SHADER); gl_->ShaderSource(vertex, 1, vert_src_arr, vert_src_arr_lens); @@ -1380,6 +1529,8 @@ void GlCoreRhi::bind_index_buffer(Handle ctx, Handle bu SRB2_ASSERT(ib.desc.type == rhi::BufferType::kIndexBuffer); + current_index_buffer_ = buffer; + gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer); } @@ -1412,11 +1563,20 @@ void GlCoreRhi::draw(Handle ctx, uint32_t vertex_count, uint32_ void GlCoreRhi::draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + + SRB2_ASSERT(current_index_buffer_ != kNullHandle); +#ifndef NDEBUG + { + auto& ib = buffer_slab_[current_index_buffer_]; + SRB2_ASSERT((index_count + first_index) * 2 + index_buffer_offset_ <= ib.desc.size); + } +#endif + gl_->DrawElements( map_primitive_mode(current_primitive_type_), index_count, GL_UNSIGNED_SHORT, - reinterpret_cast(first_index * 2 + index_buffer_offset_) + (const void*)((size_t)first_index * 2 + index_buffer_offset_) ); GL_ASSERT } diff --git a/src/rhi/gl3_core/gl3_core_rhi.hpp b/src/rhi/gl3_core/gl3_core_rhi.hpp index fa7997b8b..b5b43afa1 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.hpp +++ b/src/rhi/gl3_core/gl3_core_rhi.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_RHI_GLES2_RHI_HPP__ #define __SRB2_RHI_GLES2_RHI_HPP__ @@ -145,6 +154,8 @@ class GlCoreRhi final : public Rhi Slab uniform_set_slab_; Slab binding_set_slab_; + Handle current_index_buffer_; + std::unordered_map framebuffers_ {16}; struct DefaultRenderPassState diff --git a/src/rhi/gles2/gles2_rhi.cpp b/src/rhi/gles2/gles2_rhi.cpp index 5c8134eb6..a1eb92acf 100644 --- a/src/rhi/gles2/gles2_rhi.cpp +++ b/src/rhi/gles2/gles2_rhi.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "gles2_rhi.hpp" #include diff --git a/src/rhi/gles2/gles2_rhi.hpp b/src/rhi/gles2/gles2_rhi.hpp index 9858e770b..f912941b4 100644 --- a/src/rhi/gles2/gles2_rhi.hpp +++ b/src/rhi/gles2/gles2_rhi.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_RHI_GLES2_RHI_HPP__ #define __SRB2_RHI_GLES2_RHI_HPP__ diff --git a/src/rhi/handle.hpp b/src/rhi/handle.hpp index bda2928fa..282a924da 100644 --- a/src/rhi/handle.hpp +++ b/src/rhi/handle.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_RHI_HANDLE_HPP__ #define __SRB2_RHI_HANDLE_HPP__ diff --git a/src/rhi/rhi.cpp b/src/rhi/rhi.cpp index c63282b95..7e166246a 100644 --- a/src/rhi/rhi.cpp +++ b/src/rhi/rhi.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "rhi.hpp" #include @@ -14,8 +23,9 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsUnshaded = { ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, false}, ProgramVertexInput {VertexAttributeName::kColor, VertexAttributeFormat::kFloat4, false}}}, ProgramUniformRequirements { - {{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, - ProgramSamplerRequirements {{ProgramSamplerInput {SamplerName::kSampler0, true}}}}; + {{{{UniformName::kProjection, true}}}, + {{{UniformName::kModelView, true}, {UniformName::kTexCoord0Transform, true}}}}}, + ProgramSamplerRequirements {{{SamplerName::kSampler0, true}}}}; const ProgramRequirements srb2::rhi::kProgramRequirementsUnshadedPaletted = { ProgramVertexInputRequirements { @@ -23,9 +33,19 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsUnshadedPaletted = { ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, false}, ProgramVertexInput {VertexAttributeName::kColor, VertexAttributeFormat::kFloat4, false}}}, ProgramUniformRequirements { - {{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, + {{{{UniformName::kProjection, true}}}, + {{{UniformName::kModelView, true}, + {UniformName::kTexCoord0Transform, true}, + {UniformName::kSampler0IsIndexedAlpha, false}}}}}, ProgramSamplerRequirements { - {ProgramSamplerInput {SamplerName::kSampler0, true}, ProgramSamplerInput {SamplerName::kSampler1, true}}}}; + {{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}, {SamplerName::kSampler2, false}}}}; + +const ProgramRequirements srb2::rhi::kProgramRequirementsPostprocessWipe = { + ProgramVertexInputRequirements { + {ProgramVertexInput {VertexAttributeName::kPosition, VertexAttributeFormat::kFloat3, true}, + ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, true}}}, + ProgramUniformRequirements {{{{{UniformName::kProjection, true}, {UniformName::kModelView, true}}}}}, + ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}}}}; const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram program) noexcept { @@ -35,6 +55,8 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram return kProgramRequirementsUnshaded; case PipelineProgram::kUnshadedPaletted: return kProgramRequirementsUnshadedPaletted; + case PipelineProgram::kPostprocessWipe: + return kProgramRequirementsPostprocessWipe; default: std::terminate(); } diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index a44423aee..43659fe40 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_RHI_RHI_HPP__ #define __SRB2_RHI_RHI_HPP__ @@ -63,6 +72,7 @@ enum class UniformFormat enum class PixelFormat { kR8, + kRG8, kRGBA8, kDepth16, kStencil8 @@ -71,8 +81,10 @@ enum class PixelFormat enum class TextureFormat { kLuminance, + kLuminanceAlpha, kRGB, - kRGBA + kRGBA, + kDepth }; enum class CompareFunc @@ -152,7 +164,8 @@ enum class AttachmentStoreOp enum class PipelineProgram { kUnshaded, - kUnshadedPaletted + kUnshadedPaletted, + kPostprocessWipe }; enum class BufferType @@ -181,7 +194,8 @@ enum class UniformName kTime, kModelView, kProjection, - kTexCoord0Transform + kTexCoord0Transform, + kSampler0IsIndexedAlpha }; enum class SamplerName @@ -237,12 +251,12 @@ struct ProgramVertexInputRequirements struct ProgramUniformRequirements { - srb2::StaticVec, 4> uniform_groups; + srb2::StaticVec, 4> uniform_groups; }; struct ProgramSamplerRequirements { - std::array, kMaxSamplers> samplers; + srb2::StaticVec samplers; }; struct ProgramRequirements @@ -254,6 +268,7 @@ struct ProgramRequirements extern const ProgramRequirements kProgramRequirementsUnshaded; extern const ProgramRequirements kProgramRequirementsUnshadedPaletted; +extern const ProgramRequirements kProgramRequirementsPostprocessWipe; const ProgramRequirements& program_requirements_for_program(PipelineProgram program) noexcept; @@ -288,6 +303,8 @@ inline constexpr const UniformFormat uniform_format(UniformName name) noexcept return UniformFormat::kMat4; case UniformName::kTexCoord0Transform: return UniformFormat::kMat3; + case UniformName::kSampler0IsIndexedAlpha: + return UniformFormat::kInt; default: return UniformFormat::kFloat; } @@ -309,8 +326,8 @@ struct VertexAttributeLayoutDesc struct VertexInputDesc { - std::vector buffer_layouts; - std::vector attr_layouts; + srb2::StaticVec buffer_layouts; + srb2::StaticVec attr_layouts; }; struct UniformInputDesc @@ -489,6 +506,9 @@ struct GraphicsContext { }; +/// @brief The unpack alignment of a row span when uploading pixels to the device. +constexpr const std::size_t kPixelRowUnpackAlignment = 4; + /// @brief An active handle to a rendering device. struct Rhi { diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index bf5deb873..d86f4a4b4 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -231,7 +231,11 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool { OglSdlSurface(vid.width, vid.height); } + else #endif + { + SDL_GL_SetSwapInterval(cv_vidwait.value ? 1 : 0); + } SDL_GetWindowSize(window, &width, &height); vid.realwidth = static_cast(width); diff --git a/src/sdl/rhi_gl3_core_platform.cpp b/src/sdl/rhi_gl3_core_platform.cpp index d8c0cde33..5de7eef70 100644 --- a/src/sdl/rhi_gl3_core_platform.cpp +++ b/src/sdl/rhi_gl3_core_platform.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "rhi_gl3_core_platform.hpp" #include @@ -33,6 +42,10 @@ std::tuple SdlGlCorePlatform::find_shader_sources(rhi: vertex_lump_name = "rhi_glcore_vertex_unshadedpaletted"; fragment_lump_name = "rhi_glcore_fragment_unshadedpaletted"; break; + case rhi::PipelineProgram::kPostprocessWipe: + vertex_lump_name = "rhi_glcore_vertex_postprocesswipe"; + fragment_lump_name = "rhi_glcore_fragment_postprocesswipe"; + break; default: std::terminate(); } diff --git a/src/sdl/rhi_gl3_core_platform.hpp b/src/sdl/rhi_gl3_core_platform.hpp index 0c0f6f4f3..9522e4ba4 100644 --- a/src/sdl/rhi_gl3_core_platform.hpp +++ b/src/sdl/rhi_gl3_core_platform.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__ #define __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__ diff --git a/src/sdl/rhi_gles2_platform.cpp b/src/sdl/rhi_gles2_platform.cpp index d91a3d2bf..edf5fe201 100644 --- a/src/sdl/rhi_gles2_platform.cpp +++ b/src/sdl/rhi_gles2_platform.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "rhi_gles2_platform.hpp" #include diff --git a/src/sdl/rhi_gles2_platform.hpp b/src/sdl/rhi_gles2_platform.hpp index 19970d8f1..b434c9c2e 100644 --- a/src/sdl/rhi_gles2_platform.hpp +++ b/src/sdl/rhi_gles2_platform.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__ #define __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__ diff --git a/src/v_video.cpp b/src/v_video.cpp index 779a3a6c6..4e1f0d0f7 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -41,6 +41,8 @@ #include "k_boss.h" #include "i_time.h" +using namespace srb2; + // Each screen is [vid.width*vid.height]; UINT8 *screens[5]; // screens[0] = main display window @@ -97,8 +99,12 @@ 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 @@ -650,7 +656,7 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du } } -static cliprect_t cliprect; +static cliprect_t cliprect = {0}; const cliprect_t *V_GetClipRect(void) { @@ -771,16 +777,11 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix // Draws a patch scaled to arbitrary size. void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap) { - UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t); UINT32 alphalevel, blendmode; - fixed_t col, ofs, colfrac, rowfrac, fdup, vdup; + fixed_t vdup; INT32 dupx, dupy; - const column_t *column; - UINT8 *desttop, *dest, *deststart, *destend; - const UINT8 *source, *deststop; fixed_t pwidth; // patch width - fixed_t offx = 0; // x offset const cliprect_t *clip = V_GetClipRect(); @@ -796,8 +797,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca } #endif - patchdrawfunc = standardpdraw; - if ((blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT))) blendmode++; // realign to constants if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT))) @@ -812,15 +811,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca if (alphalevel >= 10) // Still inelegible to render? return; } - if ((v_translevel = R_GetBlendTable(blendmode, alphalevel))) - patchdrawfunc = translucentpdraw; - - v_colormap = NULL; - if (colormap) - { - v_colormap = colormap; - patchdrawfunc = (v_translevel) ? transmappedpdraw : mappedpdraw; - } dupx = vid.dupx; dupy = vid.dupy; @@ -843,11 +833,9 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca // only use one dup, to avoid stretching (har har) dupx = dupy = (dupx < dupy ? dupx : dupy); - fdup = vdup = FixedMul(dupx<>= FRACBITS; y >>= FRACBITS; - desttop += (y*vid.width) + x; } else { @@ -894,8 +874,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca { V_AdjustXYWithSnap(&x, &y, scrn, dupx, dupy); } - - desttop += (y*vid.width) + x; } if (pscale != FRACUNIT) // scale width properly @@ -908,104 +886,73 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca else pwidth = patch->width * dupx; - deststart = desttop; - destend = desttop + pwidth; + float fdupy = FIXED_TO_FLOAT(vdup); - for (col = 0; (col>>FRACBITS) < patch->width; col += colfrac, ++offx, desttop++) + float fx = x; + float fy = y; + float fx2 = fx + pwidth; + float fy2 = fy + 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) { - INT32 topdelta, prevdelta = -1; - - if (scrn & V_FLIP) // offx is measured from right edge instead of left - { - if (x+pwidth-offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION) - break; - if (x+pwidth-offx >= (clip ? clip->right : vid.width)) // don't draw off the right of the screen (WRAP PREVENTION) - continue; - } - else - { - if (x+offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION) - continue; - if (x+offx >= (clip ? clip->right : vid.width)) // don't draw off the right of the screen (WRAP PREVENTION) - break; - } - - column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS])); - - while (column->topdelta != 0xff) - { - fixed_t offy = 0; - - topdelta = column->topdelta; - if (topdelta <= prevdelta) - topdelta += prevdelta; - prevdelta = topdelta; - source = (const UINT8 *)(column) + 3; - - dest = desttop; - if (scrn & V_FLIP) - dest = deststart + (destend - dest); - topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup)); - dest += topdelta * vid.width; - - if (scrn & V_VFLIP) - { - for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy) - { - if (clip != NULL) - { - const INT32 cy = y + topdelta - offy; - - if (cy < clip->top) // don't draw off the top of the clip rect - { - dest += vid.width; - continue; - } - - if (cy >= clip->bottom) // don't draw off the bottom of the clip rect - { - dest += vid.width; - continue; - } - } - - if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) - *dest = patchdrawfunc(dest, source, ofs); - - dest += vid.width; - } - } - else - { - for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy) - { - if (clip != NULL) - { - const INT32 cy = y + topdelta + offy; - - if (cy < clip->top) // don't draw off the top of the clip rect - { - dest += vid.width; - continue; - } - - if (cy >= clip->bottom) // don't draw off the bottom of the clip rect - { - dest += vid.width; - continue; - } - } - - if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) - *dest = patchdrawfunc(dest, source, ofs); - - dest += vid.width; - } - } - - column = (const column_t *)((const UINT8 *)column + column->length + 4); - } + 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::Draw2dBlend blend = hwr2::Draw2dBlend::kModulate; + switch (blendmode) + { + case AST_MODULATE: + blend = hwr2::Draw2dBlend::kModulate; + break; + case AST_ADD: + blend = hwr2::Draw2dBlend::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::Draw2dBlend::kReverseSubtractive; + break; + case AST_REVERSESUBTRACT: + blend = hwr2::Draw2dBlend::kSubtractive; + break; + default: + blend = hwr2::Draw2dBlend::kModulate; + 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. @@ -1067,9 +1014,6 @@ void V_DrawBlock(INT32 x, INT32 y, INT32 scrn, INT32 width, INT32 height, const // void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { - UINT8 *dest; - const UINT8 *deststop; - if (rendermode == render_none) return; @@ -1122,13 +1066,18 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) if (y + h > vid.height) h = vid.height - y; - dest = screens[0] + y*vid.width + x; - deststop = screens[0] + vid.rowbytes * vid.height; - c &= 255; - for (;(--h >= 0) && dest < deststop; dest += vid.width) - memset(dest, c, w * vid.bpp); + RGBA_t color = pMasterPalette[c]; + UINT8 r = (color.rgba & 0xFF); + UINT8 g = (color.rgba & 0xFF00) >> 8; + UINT8 b = (color.rgba & 0xFF0000) >> 16; + + g_2d.begin_quad() + .patch(nullptr) + .color(r / 255.f, g / 255.f, b / 255.f, 1.f) + .rect(x, y, w, h) + .done(); } #ifdef HWRENDER @@ -1169,10 +1118,6 @@ static UINT32 V_GetHWConsBackColor(void) void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { - UINT8 *dest; - const UINT8 *deststop; - INT32 u; - UINT8 *fadetable; UINT32 alphalevel = 0; if (rendermode == render_none) @@ -1231,37 +1176,18 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) if (y + h > vid.height) h = vid.height-y; - dest = screens[0] + y*vid.width + x; - deststop = screens[0] + vid.rowbytes * vid.height; - c &= 255; - // Jimita (12-04-2018) - if (alphalevel) - { - fadetable = R_GetTranslucencyTable(alphalevel) + (c*256); - for (;(--h >= 0) && dest < deststop; dest += vid.width) - { - u = 0; - while (u < w) - { - dest[u] = fadetable[consolebgmap[dest[u]]]; - u++; - } - } - } - else - { - for (;(--h >= 0) && dest < deststop; dest += vid.width) - { - u = 0; - while (u < w) - { - dest[u] = consolebgmap[dest[u]]; - u++; - } - } - } + 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::Draw2dBlend::kModulate) + .color(r, g, b, a) + .done(); } // @@ -1273,9 +1199,7 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) // void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) { - UINT8 *dest; - const UINT8 *deststop; - INT32 w, h, wait = 0; + INT32 w, h; if (rendermode == render_none) return; @@ -1321,7 +1245,6 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) return; // zero width/height wouldn't draw anything if (x + w > vid.width) { - wait = w - (vid.width - x); w = vid.width - x; } if (y + w > vid.height) @@ -1330,18 +1253,23 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) if (h > w) h = w; - dest = screens[0] + y*vid.width + x; - deststop = screens[0] + vid.rowbytes * vid.height; - c &= 255; - for (;(--h >= 0) && dest < deststop; dest += vid.width) { - memset(dest, c, w * vid.bpp); - if (wait) - wait--; - else - w--; + 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(); } } @@ -1355,11 +1283,6 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) // void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength) { - UINT8 *dest; - const UINT8 *deststop; - INT32 u; - UINT8 *fadetable; - if (rendermode == render_none) return; @@ -1403,23 +1326,42 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U if (y + h > vid.height) h = vid.height-y; - dest = screens[0] + y*vid.width + x; - deststop = screens[0] + vid.rowbytes * vid.height; + float r; + float g; + float b; + float a; + hwr2::Draw2dBlend blendmode; - c &= 255; - - fadetable = ((color & 0xFF00) // Color is not palette index? - ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. - : ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade. - for (;(--h >= 0) && dest < deststop; dest += vid.width) + if (color & 0xFF00) { - u = 0; - while (u < w) - { - dest[u] = fadetable[dest[u]]; - u++; - } + // 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::Draw2dBlend::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::Draw2dBlend::kModulate; + } + + g_2d.begin_quad() + .blend(blendmode) + .color(r, g, b, a) + .rect(x, y, w, h) + .done(); } // @@ -1427,11 +1369,10 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U // void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum) { - INT32 u, v, dupx, dupy; - fixed_t dx, dy, xfrac, yfrac; - const UINT8 *src, *deststop; - UINT8 *flat, *dest; - size_t size, lflatsize, flatshift; + INT32 dupx; + INT32 dupy; + size_t size; + size_t lflatsize; #ifdef HWRENDER if (rendermode == render_opengl) @@ -1440,89 +1381,52 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum) return; } #endif - size = W_LumpLength(flatnum); switch (size) { case 4194304: // 2048x2048 lump lflatsize = 2048; - flatshift = 11; break; case 1048576: // 1024x1024 lump lflatsize = 1024; - flatshift = 10; break; case 262144:// 512x512 lump lflatsize = 512; - flatshift = 9; break; case 65536: // 256x256 lump lflatsize = 256; - flatshift = 8; break; case 16384: // 128x128 lump lflatsize = 128; - flatshift = 7; break; case 1024: // 32x32 lump lflatsize = 32; - flatshift = 5; break; case 256: // 16x16 lump lflatsize = 16; - flatshift = 4; break; case 64: // 8x8 lump lflatsize = 8; - flatshift = 3; break; default: // 64x64 lump lflatsize = 64; - flatshift = 6; break; } - flat = static_cast(W_CacheLumpNum(flatnum, PU_CACHE)); + float fsize = lflatsize; dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy); - dest = screens[0] + y*dupy*vid.width + x*dupx; - deststop = screens[0] + vid.rowbytes * vid.height; - - // from V_DrawScaledPatch - if (vid.width != BASEVIDWIDTH * dupx) - { - // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, - // so center this imaginary screen - dest += (vid.width - (BASEVIDWIDTH * dupx)) / 2; - } - if (vid.height != BASEVIDHEIGHT * dupy) - { - // same thing here - dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2; - } - - w *= dupx; - h *= dupy; - - dx = FixedDiv(FRACUNIT, dupx<<(FRACBITS-2)); - dy = FixedDiv(FRACUNIT, dupy<<(FRACBITS-2)); - - yfrac = 0; - for (v = 0; v < h; v++, dest += vid.width) - { - xfrac = 0; - src = flat + (((yfrac>>FRACBITS) & (lflatsize - 1)) << flatshift); - for (u = 0; u < w; u++) - { - if (&dest[u] > deststop) - return; - dest[u] = src[(xfrac>>FRACBITS)&(lflatsize-1)]; - xfrac += dx; - } - yfrac += dy; - } + 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(); } // @@ -1619,21 +1523,42 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) } #endif - { - const UINT8 *fadetable = - (color > 0xFFF0) // Grab a specific colormap palette? - ? R_GetTranslationColormap(color | 0xFFFF0000, static_cast(strength), GTC_CACHE) - : ((color & 0xFF00) // Color is not palette index? - ? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade. - : ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade. - const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height; - UINT8 *buf = screens[0]; + float r; + float g; + float b; + float a; + hwr2::Draw2dBlend blendmode; - // 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]; + 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::Draw2dBlend::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::Draw2dBlend::kModulate; + } + + g_2d.begin_quad() + .blend(blendmode) + .color(r, g, b, a) + .rect(0, 0, vid.width, vid.height) + .done(); } // @@ -1643,6 +1568,8 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) // void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) { + (void)lump; + (void)strength; #ifdef HWRENDER if (rendermode != render_soft && rendermode != render_none) { @@ -1651,57 +1578,30 @@ void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) } #endif - { - lumpnum_t lumpnum = LUMPERROR; - lighttable_t *clm = NULL; - - if (lump != NULL) - lumpnum = W_GetNumForName(lump); - else - return; - - if (lumpnum != LUMPERROR) - { - clm = static_cast(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8)); - W_ReadLump(lumpnum, clm); - - if (clm != NULL) - { - const UINT8 *fadetable = ((UINT8 *)clm + strength*256); - const UINT8 *deststop = 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; - } - } - } + // NOTE: This is not implementable in HWR2. } // Simple translucency with one color, over a set number of lines starting from the top. void V_DrawFadeConsBack(INT32 plines) { - UINT8 *deststop, *buf; - + UINT32 hwcolor = V_GetHWConsBackColor(); #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode == render_opengl) { - UINT32 hwcolor = V_GetHWConsBackColor(); HWR_DrawConsoleBack(hwcolor, plines); return; } #endif - // heavily simplified -- we don't need to know x or y position, - // just the stop position - deststop = screens[0] + vid.rowbytes * std::min(plines, vid.height); - for (buf = screens[0]; buf < deststop; ++buf) - *buf = consolebgmap[*buf]; + 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::Draw2dBlend::kModulate) + .color(r, g, b, a) + .done(); } @@ -1718,26 +1618,16 @@ void V_EncoreInvertScreen(void) } #endif - { - const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height; - UINT8 *buf = screens[0]; - - for (; buf < deststop; ++buf) - { - *buf = NearestColor( - 255 - pLocalPalette[*buf].s.red, - 255 - pLocalPalette[*buf].s.green, - 255 - pLocalPalette[*buf].s.blue - ); - } - } + g_2d.begin_quad() + .blend(hwr2::Draw2dBlend::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) { - UINT8 *deststop, *buf; - if (color >= 256 && color < 512) { if (boxheight < 0) @@ -1753,50 +1643,50 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color) if (color == INT32_MAX) color = cons_backcolor.value; + UINT32 hwcolor; + switch (color) + { + case 0: hwcolor = 0xffffff00; break; // White + case 1: hwcolor = 0x00000000; break; // Black // Note this is different from V_DrawFadeConsBack + case 2: hwcolor = 0xdeb88700; break; // Sepia + case 3: hwcolor = 0x40201000; break; // Brown + case 4: hwcolor = 0xfa807200; break; // Pink + case 5: hwcolor = 0xff69b400; break; // Raspberry + case 6: hwcolor = 0xff000000; break; // Red + case 7: hwcolor = 0xffd68300; break; // Creamsicle + case 8: hwcolor = 0xff800000; break; // Orange + case 9: hwcolor = 0xdaa52000; break; // Gold + case 10: hwcolor = 0x80800000; break; // Yellow + case 11: hwcolor = 0x00ff0000; break; // Emerald + case 12: hwcolor = 0x00800000; break; // Green + case 13: hwcolor = 0x4080ff00; break; // Cyan + case 14: hwcolor = 0x4682b400; break; // Steel + case 15: hwcolor = 0x1e90ff00; break; // Periwinkle + case 16: hwcolor = 0x0000ff00; break; // Blue + case 17: hwcolor = 0xff00ff00; break; // Purple + case 18: hwcolor = 0xee82ee00; break; // Lavender + // Default green + default: hwcolor = 0x00800000; break; + } + #ifdef HWRENDER if (rendermode == render_opengl) { - UINT32 hwcolor; - switch (color) - { - case 0: hwcolor = 0xffffff00; break; // White - case 1: hwcolor = 0x00000000; break; // Black // Note this is different from V_DrawFadeConsBack - case 2: hwcolor = 0xdeb88700; break; // Sepia - case 3: hwcolor = 0x40201000; break; // Brown - case 4: hwcolor = 0xfa807200; break; // Pink - case 5: hwcolor = 0xff69b400; break; // Raspberry - case 6: hwcolor = 0xff000000; break; // Red - case 7: hwcolor = 0xffd68300; break; // Creamsicle - case 8: hwcolor = 0xff800000; break; // Orange - case 9: hwcolor = 0xdaa52000; break; // Gold - case 10: hwcolor = 0x80800000; break; // Yellow - case 11: hwcolor = 0x00ff0000; break; // Emerald - case 12: hwcolor = 0x00800000; break; // Green - case 13: hwcolor = 0x4080ff00; break; // Cyan - case 14: hwcolor = 0x4682b400; break; // Steel - case 15: hwcolor = 0x1e90ff00; break; // Periwinkle - case 16: hwcolor = 0x0000ff00; break; // Blue - case 17: hwcolor = 0xff00ff00; break; // Purple - case 18: hwcolor = 0xee82ee00; break; // Lavender - // Default green - default: hwcolor = 0x00800000; break; - } HWR_DrawTutorialBack(hwcolor, boxheight); return; } #endif - CON_SetupBackColormapEx(color, true); + 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 - // heavily simplified -- we don't need to know x or y position, - // just the start and stop positions - buf = deststop = screens[0] + vid.rowbytes * vid.height; - if (boxheight < 0) - buf += vid.rowbytes * boxheight; - else // 4 lines of space plus gaps between and some leeway - buf -= vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5); - for (; buf < deststop; ++buf) - *buf = promptbgmap[*buf]; + 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 diff --git a/src/v_video.h b/src/v_video.h index 497f0e712..bda5920d9 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -22,6 +22,14 @@ #include "hu_stuff.h" // fonts #ifdef __cplusplus + +#include "hwr2/twodee.hpp" + +namespace srb2 +{ +extern hwr2::Twodee g_2d; +} // namespace srb2 + extern "C" { #endif diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 46e2b2a3d..6fa5e9687 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -31,4 +31,5 @@ include("cpm-libyuv.cmake") add_subdirectory(tcbrindle_span) add_subdirectory(stb_vorbis) +add_subdirectory(stb_rect_pack) add_subdirectory(glad) diff --git a/thirdparty/cpm-imgui.cmake b/thirdparty/cpm-imgui.cmake index 2afd71a2c..788643f43 100644 --- a/thirdparty/cpm-imgui.cmake +++ b/thirdparty/cpm-imgui.cmake @@ -31,5 +31,6 @@ if(imgui_ADDED) target_include_directories(imgui PUBLIC "${imgui_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/imgui_config") target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="srb2_imconfig.h") target_compile_features(imgui PUBLIC cxx_std_11) + target_link_libraries(imgui PRIVATE stb_rect_pack) add_library(imgui::imgui ALIAS imgui) endif() diff --git a/thirdparty/imgui_config/srb2_imconfig.h b/thirdparty/imgui_config/srb2_imconfig.h index 5c09001b2..48645d16c 100644 --- a/thirdparty/imgui_config/srb2_imconfig.h +++ b/thirdparty/imgui_config/srb2_imconfig.h @@ -5,6 +5,7 @@ #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS #define IMGUI_DISABLE_OBSOLETE_KEYIO +#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION // We provide needed functionalities provided by default win32 impls through the interface layer #define IMGUI_DISABLE_WIN32_FUNCTIONS diff --git a/thirdparty/stb_rect_pack/CMakeLists.txt b/thirdparty/stb_rect_pack/CMakeLists.txt new file mode 100644 index 000000000..b0e610c95 --- /dev/null +++ b/thirdparty/stb_rect_pack/CMakeLists.txt @@ -0,0 +1,3 @@ +# Update from https://github.com/nothings/stb +add_library(stb_rect_pack STATIC stb_rect_pack.c include/stb_rect_pack.h) +target_include_directories(stb_rect_pack PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/thirdparty/stb_rect_pack/include/stb_rect_pack.h b/thirdparty/stb_rect_pack/include/stb_rect_pack.h new file mode 100644 index 000000000..6a633ce66 --- /dev/null +++ b/thirdparty/stb_rect_pack/include/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; + context->extra[1].y = (1<<30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/thirdparty/stb_rect_pack/stb_rect_pack.c b/thirdparty/stb_rect_pack/stb_rect_pack.c new file mode 100644 index 000000000..3f3391d6f --- /dev/null +++ b/thirdparty/stb_rect_pack/stb_rect_pack.c @@ -0,0 +1,2 @@ +#define STB_RECT_PACK_IMPLEMENTATION +#include "include/stb_rect_pack.h" From b9cf1316bde656ee9286b6ce1bdf192990498965 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 11 Feb 2023 16:10:29 -0600 Subject: [PATCH 07/60] Convert m_misc.c to .cpp --- src/CMakeLists.txt | 3 +- src/m_memcpy.c | 440 +++++++++++++++++++++++++++++++++ src/{m_misc.c => m_misc.cpp} | 461 ++--------------------------------- 3 files changed, 461 insertions(+), 443 deletions(-) create mode 100644 src/m_memcpy.c rename src/{m_misc.c => m_misc.cpp} (86%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0f7e9c479..9055926d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 m_cond.c m_easing.c m_fixed.c - m_misc.c + m_memcpy.c + m_misc.cpp m_perfstats.c m_random.c m_queue.c diff --git a/src/m_memcpy.c b/src/m_memcpy.c new file mode 100644 index 000000000..d24f00f94 --- /dev/null +++ b/src/m_memcpy.c @@ -0,0 +1,440 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file m_memcpy.c +/// \brief X86 optimized implementations of M_Memcpy + +#include "doomdef.h" +#include "m_misc.h" + +#if defined (__GNUC__) && defined (__i386__) // from libkwave, under GPL +// Alam: note libkwave memcpy code comes from mplayer's libvo/aclib_template.c, r699 + +/* for small memory blocks (<256 bytes) this version is faster */ +#define small_memcpy(dest,src,n)\ +{\ +register unsigned long int dummy;\ +__asm__ __volatile__(\ + "cld\n\t"\ + "rep; movsb"\ + :"=&D"(dest), "=&S"(src), "=&c"(dummy)\ + :"0" (dest), "1" (src),"2" (n)\ + : "memory", "cc");\ +} + +/* linux kernel __memcpy (from: /include/asm/string.h) */ +ATTRINLINE static FUNCINLINE void *__memcpy (void *dest, const void * src, size_t n) +{ + int d0, d1, d2; + + if ( n < 4 ) + { + small_memcpy(dest, src, n); + } + else + { + __asm__ __volatile__ ( + "rep ; movsl;" + "testb $2,%b4;" + "je 1f;" + "movsw;" + "1:\ttestb $1,%b4;" + "je 2f;" + "movsb;" + "2:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + :"0" (n/4), "q" (n),"1" ((long) dest),"2" ((long) src) + : "memory"); + } + + return dest; +} + +#define SSE_MMREG_SIZE 16 +#define MMX_MMREG_SIZE 8 + +#define MMX1_MIN_LEN 0x800 /* 2K blocks */ +#define MIN_LEN 0x40 /* 64-byte blocks */ + +/* SSE note: i tried to move 128 bytes a time instead of 64 but it +didn't make any measureable difference. i'm using 64 for the sake of +simplicity. [MF] */ +static /*FUNCTARGET("sse2")*/ void *sse_cpy(void * dest, const void * src, size_t n) +{ + void *retval = dest; + size_t i; + + /* PREFETCH has effect even for MOVSB instruction ;) */ + __asm__ __volatile__ ( + "prefetchnta (%0);" + "prefetchnta 32(%0);" + "prefetchnta 64(%0);" + "prefetchnta 96(%0);" + "prefetchnta 128(%0);" + "prefetchnta 160(%0);" + "prefetchnta 192(%0);" + "prefetchnta 224(%0);" + "prefetchnta 256(%0);" + "prefetchnta 288(%0);" + : : "r" (src) ); + + if (n >= MIN_LEN) + { + register unsigned long int delta; + /* Align destinition to MMREG_SIZE -boundary */ + delta = ((unsigned long int)dest)&(SSE_MMREG_SIZE-1); + if (delta) + { + delta=SSE_MMREG_SIZE-delta; + n -= delta; + small_memcpy(dest, src, delta); + } + i = n >> 6; /* n/64 */ + n&=63; + if (((unsigned long)src) & 15) + /* if SRC is misaligned */ + for (; i>0; i--) + { + __asm__ __volatile__ ( + "prefetchnta 320(%0);" + "prefetchnta 352(%0);" + "movups (%0), %%xmm0;" + "movups 16(%0), %%xmm1;" + "movups 32(%0), %%xmm2;" + "movups 48(%0), %%xmm3;" + "movntps %%xmm0, (%1);" + "movntps %%xmm1, 16(%1);" + "movntps %%xmm2, 32(%1);" + "movntps %%xmm3, 48(%1);" + :: "r" (src), "r" (dest) : "memory"); + src = (const unsigned char *)src + 64; + dest = (unsigned char *)dest + 64; + } + else + /* + Only if SRC is aligned on 16-byte boundary. + It allows to use movaps instead of movups, which required data + to be aligned or a general-protection exception (#GP) is generated. + */ + for (; i>0; i--) + { + __asm__ __volatile__ ( + "prefetchnta 320(%0);" + "prefetchnta 352(%0);" + "movaps (%0), %%xmm0;" + "movaps 16(%0), %%xmm1;" + "movaps 32(%0), %%xmm2;" + "movaps 48(%0), %%xmm3;" + "movntps %%xmm0, (%1);" + "movntps %%xmm1, 16(%1);" + "movntps %%xmm2, 32(%1);" + "movntps %%xmm3, 48(%1);" + :: "r" (src), "r" (dest) : "memory"); + src = ((const unsigned char *)src) + 64; + dest = ((unsigned char *)dest) + 64; + } + /* since movntq is weakly-ordered, a "sfence" + * is needed to become ordered again. */ + __asm__ __volatile__ ("sfence":::"memory"); + /* enables to use FPU */ + __asm__ __volatile__ ("emms":::"memory"); + } + /* + * Now do the tail of the block + */ + if (n) __memcpy(dest, src, n); + return retval; +} + +static FUNCTARGET("mmx") void *mmx2_cpy(void *dest, const void *src, size_t n) +{ + void *retval = dest; + size_t i; + + /* PREFETCH has effect even for MOVSB instruction ;) */ + __asm__ __volatile__ ( + "prefetchnta (%0);" + "prefetchnta 32(%0);" + "prefetchnta 64(%0);" + "prefetchnta 96(%0);" + "prefetchnta 128(%0);" + "prefetchnta 160(%0);" + "prefetchnta 192(%0);" + "prefetchnta 224(%0);" + "prefetchnta 256(%0);" + "prefetchnta 288(%0);" + : : "r" (src)); + + if (n >= MIN_LEN) + { + register unsigned long int delta; + /* Align destinition to MMREG_SIZE -boundary */ + delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); + if (delta) + { + delta=MMX_MMREG_SIZE-delta; + n -= delta; + small_memcpy(dest, src, delta); + } + i = n >> 6; /* n/64 */ + n&=63; + for (; i>0; i--) + { + __asm__ __volatile__ ( + "prefetchnta 320(%0);" + "prefetchnta 352(%0);" + "movq (%0), %%mm0;" + "movq 8(%0), %%mm1;" + "movq 16(%0), %%mm2;" + "movq 24(%0), %%mm3;" + "movq 32(%0), %%mm4;" + "movq 40(%0), %%mm5;" + "movq 48(%0), %%mm6;" + "movq 56(%0), %%mm7;" + "movntq %%mm0, (%1);" + "movntq %%mm1, 8(%1);" + "movntq %%mm2, 16(%1);" + "movntq %%mm3, 24(%1);" + "movntq %%mm4, 32(%1);" + "movntq %%mm5, 40(%1);" + "movntq %%mm6, 48(%1);" + "movntq %%mm7, 56(%1);" + :: "r" (src), "r" (dest) : "memory"); + src = ((const unsigned char *)src) + 64; + dest = ((unsigned char *)dest) + 64; + } + /* since movntq is weakly-ordered, a "sfence" + * is needed to become ordered again. */ + __asm__ __volatile__ ("sfence":::"memory"); + __asm__ __volatile__ ("emms":::"memory"); + } + /* + * Now do the tail of the block + */ + if (n) __memcpy(dest, src, n); + return retval; +} + +static FUNCTARGET("mmx") void *mmx1_cpy(void *dest, const void *src, size_t n) //3DNOW +{ + void *retval = dest; + size_t i; + + /* PREFETCH has effect even for MOVSB instruction ;) */ + __asm__ __volatile__ ( + "prefetch (%0);" + "prefetch 32(%0);" + "prefetch 64(%0);" + "prefetch 96(%0);" + "prefetch 128(%0);" + "prefetch 160(%0);" + "prefetch 192(%0);" + "prefetch 224(%0);" + "prefetch 256(%0);" + "prefetch 288(%0);" + : : "r" (src)); + + if (n >= MMX1_MIN_LEN) + { + register unsigned long int delta; + /* Align destinition to MMREG_SIZE -boundary */ + delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); + if (delta) + { + delta=MMX_MMREG_SIZE-delta; + n -= delta; + small_memcpy(dest, src, delta); + } + i = n >> 6; /* n/64 */ + n&=63; + for (; i>0; i--) + { + __asm__ __volatile__ ( + "prefetch 320(%0);" + "prefetch 352(%0);" + "movq (%0), %%mm0;" + "movq 8(%0), %%mm1;" + "movq 16(%0), %%mm2;" + "movq 24(%0), %%mm3;" + "movq 32(%0), %%mm4;" + "movq 40(%0), %%mm5;" + "movq 48(%0), %%mm6;" + "movq 56(%0), %%mm7;" + "movq %%mm0, (%1);" + "movq %%mm1, 8(%1);" + "movq %%mm2, 16(%1);" + "movq %%mm3, 24(%1);" + "movq %%mm4, 32(%1);" + "movq %%mm5, 40(%1);" + "movq %%mm6, 48(%1);" + "movq %%mm7, 56(%1);" + :: "r" (src), "r" (dest) : "memory"); + src = ((const unsigned char *)src) + 64; + dest = ((unsigned char *)dest) + 64; + } + __asm__ __volatile__ ("femms":::"memory"); // same as mmx_cpy() but with a femms + } + /* + * Now do the tail of the block + */ + if (n) __memcpy(dest, src, n); + return retval; +} +#endif + +// Alam: why? memcpy may be __cdecl/_System and our code may be not the same type +static void *cpu_cpy(void *dest, const void *src, size_t n) +{ + if (src == NULL) + { + CONS_Debug(DBG_MEMORY, "Memcpy from 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); + return dest; + } + + if(dest == NULL) + { + CONS_Debug(DBG_MEMORY, "Memcpy to 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); + return dest; + } + + return memcpy(dest, src, n); +} + +static /*FUNCTARGET("mmx")*/ void *mmx_cpy(void *dest, const void *src, size_t n) +{ +#if defined (_MSC_VER) && defined (_X86_) + _asm + { + mov ecx, [n] + mov esi, [src] + mov edi, [dest] + shr ecx, 6 // mit mmx: 64bytes per iteration + jz lower_64 // if lower than 64 bytes + loop_64: // MMX transfers multiples of 64bytes + movq mm0, 0[ESI] // read sources + movq mm1, 8[ESI] + movq mm2, 16[ESI] + movq mm3, 24[ESI] + movq mm4, 32[ESI] + movq mm5, 40[ESI] + movq mm6, 48[ESI] + movq mm7, 56[ESI] + + movq 0[EDI], mm0 // write destination + movq 8[EDI], mm1 + movq 16[EDI], mm2 + movq 24[EDI], mm3 + movq 32[EDI], mm4 + movq 40[EDI], mm5 + movq 48[EDI], mm6 + movq 56[EDI], mm7 + + add esi, 64 + add edi, 64 + dec ecx + jnz loop_64 + emms // close mmx operation + lower_64:// transfer rest of buffer + mov ebx,esi + sub ebx,src + mov ecx,[n] + sub ecx,ebx + shr ecx, 3 // multiples of 8 bytes + jz lower_8 + loop_8: + movq mm0, [esi] // read source + movq [edi], mm0 // write destination + add esi, 8 + add edi, 8 + dec ecx + jnz loop_8 + emms // close mmx operation + lower_8: + mov ebx,esi + sub ebx,src + mov ecx,[n] + sub ecx,ebx + rep movsb + mov eax, [dest] // return dest + } +#elif defined (__GNUC__) && defined (__i386__) + void *retval = dest; + size_t i; + + if (n >= MMX1_MIN_LEN) + { + register unsigned long int delta; + /* Align destinition to MMREG_SIZE -boundary */ + delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); + if (delta) + { + delta=MMX_MMREG_SIZE-delta; + n -= delta; + small_memcpy(dest, src, delta); + } + i = n >> 6; /* n/64 */ + n&=63; + for (; i>0; i--) + { + __asm__ __volatile__ ( + "movq (%0), %%mm0;" + "movq 8(%0), %%mm1;" + "movq 16(%0), %%mm2;" + "movq 24(%0), %%mm3;" + "movq 32(%0), %%mm4;" + "movq 40(%0), %%mm5;" + "movq 48(%0), %%mm6;" + "movq 56(%0), %%mm7;" + "movq %%mm0, (%1);" + "movq %%mm1, 8(%1);" + "movq %%mm2, 16(%1);" + "movq %%mm3, 24(%1);" + "movq %%mm4, 32(%1);" + "movq %%mm5, 40(%1);" + "movq %%mm6, 48(%1);" + "movq %%mm7, 56(%1);" + :: "r" (src), "r" (dest) : "memory"); + src = ((const unsigned char *)src) + 64; + dest = ((unsigned char *)dest) + 64; + } + __asm__ __volatile__ ("emms":::"memory"); + } + /* + * Now do the tail of the block + */ + if (n) __memcpy(dest, src, n); + return retval; +#else + return cpu_cpy(dest, src, n); +#endif +} + +void *(*M_Memcpy)(void* dest, const void* src, size_t n) = cpu_cpy; + +/** Memcpy that uses MMX, 3DNow, MMXExt or even SSE + * Do not use on overlapped memory, use memmove for that + */ +void M_SetupMemcpy(void) +{ +#if defined (__GNUC__) && defined (__i386__) + if (R_SSE2) + M_Memcpy = sse_cpy; + else if (R_MMXExt) + M_Memcpy = mmx2_cpy; + else if (R_3DNow) + M_Memcpy = mmx1_cpy; + else +#endif + if (R_MMX) + M_Memcpy = mmx_cpy; +#if 0 + M_Memcpy = cpu_cpy; +#endif +} diff --git a/src/m_misc.c b/src/m_misc.cpp similarity index 86% rename from src/m_misc.c rename to src/m_misc.cpp index 53e3534ac..60e5a2b65 100644 --- a/src/m_misc.c +++ b/src/m_misc.cpp @@ -8,7 +8,7 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file m_misc.h +/// \file m_misc.cpp /// \brief Commonly used routines /// Default config file, PCX screenshots, file i/o @@ -23,6 +23,7 @@ #include #endif +#include #include // Extended map support. @@ -308,7 +309,7 @@ size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag) length = ftell(handle); fseek(handle,0,SEEK_SET); - buf = Z_Malloc(length + 1, tag, NULL); + buf = static_cast(Z_Malloc(length + 1, tag, NULL)); count = fread(buf, 1, length, handle); fclose(handle); @@ -472,7 +473,7 @@ void M_SaveJoinedIPs(void) { FILE *f = NULL; UINT8 i; - const char *filepath = va("%s"PATHSEP"%s", srb2home, IPLOGFILE); + const char *filepath = va("%s" PATHSEP "%s", srb2home, IPLOGFILE); if (!*joinedIPlist[0][0]) return; // Don't bother, there's nothing to save. @@ -506,7 +507,7 @@ void M_LoadJoinedIPs(void) char *s; char buffer[2*(MAX_LOGIP+1)]; - filepath = va("%s"PATHSEP"%s", srb2home, IPLOGFILE); + filepath = va("%s" PATHSEP "%s", srb2home, IPLOGFILE); f = fopen(filepath, "r"); if (f == NULL) @@ -774,8 +775,8 @@ static void M_CreateScreenShotPalette(void) for (i = 0, j = 0; i < 768; i += 3, j++) { RGBA_t locpal = ((cv_screenshot_colorprofile.value) - ? pLocalPalette[(max(st_palette,0)*256)+j] - : pMasterPalette[(max(st_palette,0)*256)+j]); + ? pLocalPalette[(std::max(st_palette,0)*256)+j] + : pMasterPalette[(std::max(st_palette,0)*256)+j]); screenshot_palette[i] = locpal.s.red; screenshot_palette[i+1] = locpal.s.green; screenshot_palette[i+2] = locpal.s.blue; @@ -854,7 +855,7 @@ static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_ const png_byte png_interlace = PNG_INTERLACE_NONE; //PNG_INTERLACE_ADAM7 if (palette) { - png_colorp png_PLTE = png_malloc(png_ptr, sizeof(png_color)*256); //palette + png_colorp png_PLTE = static_cast(png_malloc(png_ptr, sizeof(png_color)*256)); //palette png_uint_16 i; const png_byte *pal = palette; @@ -975,8 +976,8 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png static inline void M_PNGImage(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 height, png_bytep png_buf) { - png_uint_32 pitch = png_get_rowbytes(png_ptr, png_info_ptr); - png_bytepp row_pointers = png_malloc(png_ptr, height* sizeof (png_bytep)); + png_uint_32 pitch = png_get_rowbytes(png_ptr, static_cast(png_info_ptr)); + png_bytepp row_pointers = static_cast(png_malloc(png_ptr, height* sizeof (png_bytep))); png_uint_32 y; for (y = 0; y < height; y++) { @@ -1334,7 +1335,7 @@ void M_StartMovie(void) if (cv_movie_option.value != 3) { - strcat(pathname, PATHSEP"media"PATHSEP"movies"PATHSEP); + strcat(pathname, PATHSEP "media" PATHSEP "movies" PATHSEP); M_MkdirEach(pathname, M_PathParts(pathname) - 2, 0755); } @@ -1579,7 +1580,7 @@ boolean M_SavePNG(const char *filename, void *data, int width, int height, const png_write_info(png_ptr, png_info_ptr); - M_PNGImage(png_ptr, png_info_ptr, height, data); + M_PNGImage(png_ptr, png_info_ptr, height, static_cast(data)); png_write_end(png_ptr, png_info_ptr); png_destroy_write_struct(&png_ptr, &png_info_ptr); @@ -1719,7 +1720,7 @@ void M_DoScreenShot(void) if (cv_screenshot_option.value != 3) { - strcat(pathname, PATHSEP"media"PATHSEP"screenshots"PATHSEP); + strcat(pathname, PATHSEP "media" PATHSEP "screenshots" PATHSEP); M_MkdirEach(pathname, M_PathParts(pathname) - 2, 0755); } @@ -2462,462 +2463,38 @@ TMatrix *RotateZMatrix(angle_t rad) char *sizeu1(size_t num) { static char sizeu1_buf[28]; - sprintf(sizeu1_buf, "%"PRIdS, num); + sprintf(sizeu1_buf, "%" PRIdS, num); return sizeu1_buf; } char *sizeu2(size_t num) { static char sizeu2_buf[28]; - sprintf(sizeu2_buf, "%"PRIdS, num); + sprintf(sizeu2_buf, "%" PRIdS, num); return sizeu2_buf; } char *sizeu3(size_t num) { static char sizeu3_buf[28]; - sprintf(sizeu3_buf, "%"PRIdS, num); + sprintf(sizeu3_buf, "%" PRIdS, num); return sizeu3_buf; } char *sizeu4(size_t num) { static char sizeu4_buf[28]; - sprintf(sizeu4_buf, "%"PRIdS, num); + sprintf(sizeu4_buf, "%" PRIdS, num); return sizeu4_buf; } char *sizeu5(size_t num) { static char sizeu5_buf[28]; - sprintf(sizeu5_buf, "%"PRIdS, num); + sprintf(sizeu5_buf, "%" PRIdS, num); return sizeu5_buf; } -#if defined (__GNUC__) && defined (__i386__) // from libkwave, under GPL -// Alam: note libkwave memcpy code comes from mplayer's libvo/aclib_template.c, r699 - -/* for small memory blocks (<256 bytes) this version is faster */ -#define small_memcpy(dest,src,n)\ -{\ -register unsigned long int dummy;\ -__asm__ __volatile__(\ - "cld\n\t"\ - "rep; movsb"\ - :"=&D"(dest), "=&S"(src), "=&c"(dummy)\ - :"0" (dest), "1" (src),"2" (n)\ - : "memory", "cc");\ -} -/* linux kernel __memcpy (from: /include/asm/string.h) */ -ATTRINLINE static FUNCINLINE void *__memcpy (void *dest, const void * src, size_t n) -{ - int d0, d1, d2; - - if ( n < 4 ) - { - small_memcpy(dest, src, n); - } - else - { - __asm__ __volatile__ ( - "rep ; movsl;" - "testb $2,%b4;" - "je 1f;" - "movsw;" - "1:\ttestb $1,%b4;" - "je 2f;" - "movsb;" - "2:" - : "=&c" (d0), "=&D" (d1), "=&S" (d2) - :"0" (n/4), "q" (n),"1" ((long) dest),"2" ((long) src) - : "memory"); - } - - return dest; -} - -#define SSE_MMREG_SIZE 16 -#define MMX_MMREG_SIZE 8 - -#define MMX1_MIN_LEN 0x800 /* 2K blocks */ -#define MIN_LEN 0x40 /* 64-byte blocks */ - -/* SSE note: i tried to move 128 bytes a time instead of 64 but it -didn't make any measureable difference. i'm using 64 for the sake of -simplicity. [MF] */ -static /*FUNCTARGET("sse2")*/ void *sse_cpy(void * dest, const void * src, size_t n) -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetchnta (%0);" - "prefetchnta 32(%0);" - "prefetchnta 64(%0);" - "prefetchnta 96(%0);" - "prefetchnta 128(%0);" - "prefetchnta 160(%0);" - "prefetchnta 192(%0);" - "prefetchnta 224(%0);" - "prefetchnta 256(%0);" - "prefetchnta 288(%0);" - : : "r" (src) ); - - if (n >= MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(SSE_MMREG_SIZE-1); - if (delta) - { - delta=SSE_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - if (((unsigned long)src) & 15) - /* if SRC is misaligned */ - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movups (%0), %%xmm0;" - "movups 16(%0), %%xmm1;" - "movups 32(%0), %%xmm2;" - "movups 48(%0), %%xmm3;" - "movntps %%xmm0, (%1);" - "movntps %%xmm1, 16(%1);" - "movntps %%xmm2, 32(%1);" - "movntps %%xmm3, 48(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = (const unsigned char *)src + 64; - dest = (unsigned char *)dest + 64; - } - else - /* - Only if SRC is aligned on 16-byte boundary. - It allows to use movaps instead of movups, which required data - to be aligned or a general-protection exception (#GP) is generated. - */ - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movaps (%0), %%xmm0;" - "movaps 16(%0), %%xmm1;" - "movaps 32(%0), %%xmm2;" - "movaps 48(%0), %%xmm3;" - "movntps %%xmm0, (%1);" - "movntps %%xmm1, 16(%1);" - "movntps %%xmm2, 32(%1);" - "movntps %%xmm3, 48(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - /* since movntq is weakly-ordered, a "sfence" - * is needed to become ordered again. */ - __asm__ __volatile__ ("sfence":::"memory"); - /* enables to use FPU */ - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} - -static FUNCTARGET("mmx") void *mmx2_cpy(void *dest, const void *src, size_t n) -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetchnta (%0);" - "prefetchnta 32(%0);" - "prefetchnta 64(%0);" - "prefetchnta 96(%0);" - "prefetchnta 128(%0);" - "prefetchnta 160(%0);" - "prefetchnta 192(%0);" - "prefetchnta 224(%0);" - "prefetchnta 256(%0);" - "prefetchnta 288(%0);" - : : "r" (src)); - - if (n >= MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetchnta 320(%0);" - "prefetchnta 352(%0);" - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movntq %%mm0, (%1);" - "movntq %%mm1, 8(%1);" - "movntq %%mm2, 16(%1);" - "movntq %%mm3, 24(%1);" - "movntq %%mm4, 32(%1);" - "movntq %%mm5, 40(%1);" - "movntq %%mm6, 48(%1);" - "movntq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - /* since movntq is weakly-ordered, a "sfence" - * is needed to become ordered again. */ - __asm__ __volatile__ ("sfence":::"memory"); - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} - -static FUNCTARGET("mmx") void *mmx1_cpy(void *dest, const void *src, size_t n) //3DNOW -{ - void *retval = dest; - size_t i; - - /* PREFETCH has effect even for MOVSB instruction ;) */ - __asm__ __volatile__ ( - "prefetch (%0);" - "prefetch 32(%0);" - "prefetch 64(%0);" - "prefetch 96(%0);" - "prefetch 128(%0);" - "prefetch 160(%0);" - "prefetch 192(%0);" - "prefetch 224(%0);" - "prefetch 256(%0);" - "prefetch 288(%0);" - : : "r" (src)); - - if (n >= MMX1_MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "prefetch 320(%0);" - "prefetch 352(%0);" - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movq %%mm0, (%1);" - "movq %%mm1, 8(%1);" - "movq %%mm2, 16(%1);" - "movq %%mm3, 24(%1);" - "movq %%mm4, 32(%1);" - "movq %%mm5, 40(%1);" - "movq %%mm6, 48(%1);" - "movq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - __asm__ __volatile__ ("femms":::"memory"); // same as mmx_cpy() but with a femms - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -} -#endif - -// Alam: why? memcpy may be __cdecl/_System and our code may be not the same type -static void *cpu_cpy(void *dest, const void *src, size_t n) -{ - if (src == NULL) - { - CONS_Debug(DBG_MEMORY, "Memcpy from 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); - return dest; - } - - if(dest == NULL) - { - CONS_Debug(DBG_MEMORY, "Memcpy to 0x0?!: %p %p %s\n", dest, src, sizeu1(n)); - return dest; - } - - return memcpy(dest, src, n); -} - -static /*FUNCTARGET("mmx")*/ void *mmx_cpy(void *dest, const void *src, size_t n) -{ -#if defined (_MSC_VER) && defined (_X86_) - _asm - { - mov ecx, [n] - mov esi, [src] - mov edi, [dest] - shr ecx, 6 // mit mmx: 64bytes per iteration - jz lower_64 // if lower than 64 bytes - loop_64: // MMX transfers multiples of 64bytes - movq mm0, 0[ESI] // read sources - movq mm1, 8[ESI] - movq mm2, 16[ESI] - movq mm3, 24[ESI] - movq mm4, 32[ESI] - movq mm5, 40[ESI] - movq mm6, 48[ESI] - movq mm7, 56[ESI] - - movq 0[EDI], mm0 // write destination - movq 8[EDI], mm1 - movq 16[EDI], mm2 - movq 24[EDI], mm3 - movq 32[EDI], mm4 - movq 40[EDI], mm5 - movq 48[EDI], mm6 - movq 56[EDI], mm7 - - add esi, 64 - add edi, 64 - dec ecx - jnz loop_64 - emms // close mmx operation - lower_64:// transfer rest of buffer - mov ebx,esi - sub ebx,src - mov ecx,[n] - sub ecx,ebx - shr ecx, 3 // multiples of 8 bytes - jz lower_8 - loop_8: - movq mm0, [esi] // read source - movq [edi], mm0 // write destination - add esi, 8 - add edi, 8 - dec ecx - jnz loop_8 - emms // close mmx operation - lower_8: - mov ebx,esi - sub ebx,src - mov ecx,[n] - sub ecx,ebx - rep movsb - mov eax, [dest] // return dest - } -#elif defined (__GNUC__) && defined (__i386__) - void *retval = dest; - size_t i; - - if (n >= MMX1_MIN_LEN) - { - register unsigned long int delta; - /* Align destinition to MMREG_SIZE -boundary */ - delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1); - if (delta) - { - delta=MMX_MMREG_SIZE-delta; - n -= delta; - small_memcpy(dest, src, delta); - } - i = n >> 6; /* n/64 */ - n&=63; - for (; i>0; i--) - { - __asm__ __volatile__ ( - "movq (%0), %%mm0;" - "movq 8(%0), %%mm1;" - "movq 16(%0), %%mm2;" - "movq 24(%0), %%mm3;" - "movq 32(%0), %%mm4;" - "movq 40(%0), %%mm5;" - "movq 48(%0), %%mm6;" - "movq 56(%0), %%mm7;" - "movq %%mm0, (%1);" - "movq %%mm1, 8(%1);" - "movq %%mm2, 16(%1);" - "movq %%mm3, 24(%1);" - "movq %%mm4, 32(%1);" - "movq %%mm5, 40(%1);" - "movq %%mm6, 48(%1);" - "movq %%mm7, 56(%1);" - :: "r" (src), "r" (dest) : "memory"); - src = ((const unsigned char *)src) + 64; - dest = ((unsigned char *)dest) + 64; - } - __asm__ __volatile__ ("emms":::"memory"); - } - /* - * Now do the tail of the block - */ - if (n) __memcpy(dest, src, n); - return retval; -#else - return cpu_cpy(dest, src, n); -#endif -} - -void *(*M_Memcpy)(void* dest, const void* src, size_t n) = cpu_cpy; - -/** Memcpy that uses MMX, 3DNow, MMXExt or even SSE - * Do not use on overlapped memory, use memmove for that - */ -void M_SetupMemcpy(void) -{ -#if defined (__GNUC__) && defined (__i386__) - if (R_SSE2) - M_Memcpy = sse_cpy; - else if (R_MMXExt) - M_Memcpy = mmx2_cpy; - else if (R_3DNow) - M_Memcpy = mmx1_cpy; - else -#endif - if (R_MMX) - M_Memcpy = mmx_cpy; -#if 0 - M_Memcpy = cpu_cpy; -#endif -} - /** Return the appropriate message for a file error or end of file. */ const char *M_FileError(FILE *fp) @@ -3026,7 +2603,7 @@ int M_JumpWord(const char *line) if (isspace(line[1])) return 1 + strspn(&line[1], " "); else - return strcspn(line, " "PUNCTUATION); + return strcspn(line, " " PUNCTUATION); } } From e875c8e20db9f1c21e94d67e75ae29970f3550ae Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 11 Feb 2023 17:35:33 -0600 Subject: [PATCH 08/60] hw2: add screenshot pass --- src/d_main.c | 6 ++- src/g_game.c | 4 +- src/hwr2/CMakeLists.txt | 2 + src/hwr2/pass_screenshot.cpp | 68 +++++++++++++++++++++++++++++++ src/hwr2/pass_screenshot.hpp | 49 ++++++++++++++++++++++ src/i_video_common.cpp | 15 ++++++- src/m_misc.cpp | 24 +++++------ src/m_misc.h | 13 +++++- src/rhi/gl3_core/gl3_core_rhi.cpp | 23 +++++++++-- src/rhi/gl3_core/gl3_core_rhi.hpp | 3 +- src/rhi/rhi.hpp | 4 +- 11 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 src/hwr2/pass_screenshot.cpp create mode 100644 src/hwr2/pass_screenshot.hpp diff --git a/src/d_main.c b/src/d_main.c index 7cbddf194..229de62b7 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -868,8 +868,10 @@ void D_SRB2Loop(void) // Only take screenshots after drawing. if (moviemode) M_SaveFrame(); - if (takescreenshot) - M_DoScreenShot(); +#ifdef HWRENDER + if (rendermode == render_opengl && takescreenshot) + M_DoLegacyGLScreenShot(); +#endif // consoleplayer -> displayplayers (hear sounds from viewpoint) S_UpdateSounds(); // move positional sounds diff --git a/src/g_game.c b/src/g_game.c index 441f9e6c6..706c9fdb5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1603,8 +1603,6 @@ void G_PreLevelTitleCard(void) if (moviemode) M_SaveFrame(); - if (takescreenshot) // Only take screenshots after drawing. - M_DoScreenShot(); while (!((nowtime = I_GetTime()) - lasttime)) { @@ -4396,7 +4394,7 @@ void G_LoadGameData(void) { // Don't load, but do save. (essentially, reset) gamedata->loaded = true; - return; + return; } if (P_SaveBufferFromFile(&save, va(pandf, srb2home, gamedatafilename)) == false) diff --git a/src/hwr2/CMakeLists.txt b/src/hwr2/CMakeLists.txt index 34aa2186f..0500c9ee4 100644 --- a/src/hwr2/CMakeLists.txt +++ b/src/hwr2/CMakeLists.txt @@ -9,6 +9,8 @@ target_sources(SRB2SDL2 PRIVATE pass_postprocess.hpp pass_resource_managers.cpp pass_resource_managers.hpp + pass_screenshot.cpp + pass_screenshot.hpp pass_software.cpp pass_software.hpp pass_twodee.cpp diff --git a/src/hwr2/pass_screenshot.cpp b/src/hwr2/pass_screenshot.cpp new file mode 100644 index 000000000..bd9a69bf6 --- /dev/null +++ b/src/hwr2/pass_screenshot.cpp @@ -0,0 +1,68 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "pass_screenshot.hpp" + +#include + +#include "../m_misc.h" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +ScreenshotPass::ScreenshotPass() = default; +ScreenshotPass::~ScreenshotPass() = default; + +void ScreenshotPass::prepass(Rhi& rhi) +{ + if (!render_pass_) + { + render_pass_ = rhi.create_render_pass( + { + std::nullopt, + PixelFormat::kRGBA8, + AttachmentLoadOp::kLoad, + AttachmentStoreOp::kStore + } + ); + } + + doing_screenshot_ = takescreenshot; +} + +void ScreenshotPass::transfer(Rhi& rhi, Handle ctx) +{ +} + +void ScreenshotPass::graphics(Rhi& rhi, Handle ctx) +{ + if (!doing_screenshot_) + { + return; + } + + pixel_data_.clear(); + pixel_data_.resize(width_ * height_ * 3); // 3 bytes per pixel for RGB8 + + rhi.begin_render_pass(ctx, {render_pass_, source_, std::nullopt, {0.f, 0.f, 0.f, 0.f}}); + rhi.read_pixels(ctx, {0, 0, width_, height_}, PixelFormat::kRGB8, tcb::as_writable_bytes(tcb::span(pixel_data_))); + rhi.end_render_pass(ctx); +} + +void ScreenshotPass::postpass(Rhi& rhi) +{ + if (!doing_screenshot_) + { + return; + } + + M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); + doing_screenshot_ = false; +} diff --git a/src/hwr2/pass_screenshot.hpp b/src/hwr2/pass_screenshot.hpp new file mode 100644 index 000000000..79de8b654 --- /dev/null +++ b/src/hwr2/pass_screenshot.hpp @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_PASS_SCREENSHOT_HPP__ +#define __SRB2_HWR2_PASS_SCREENSHOT_HPP__ + +#include +#include + +#include "pass.hpp" + +namespace srb2::hwr2 +{ + +class ScreenshotPass : public Pass +{ + bool doing_screenshot_ = false; + rhi::Handle source_; + rhi::Handle render_pass_; + std::vector pixel_data_; + uint32_t width_ = 0; + uint32_t height_ = 0; + +public: + ScreenshotPass(); + virtual ~ScreenshotPass(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + void set_source(rhi::Handle source, uint32_t width, uint32_t height) + { + source_ = source; + width_ = width; + height_ = height; + } +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_PASS_SCREENSHOT_HPP__ diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 040c477c0..844d86cdf 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -22,6 +22,7 @@ #include "hwr2/pass_manager.hpp" #include "hwr2/pass_postprocess.hpp" #include "hwr2/pass_resource_managers.hpp" +#include "hwr2/pass_screenshot.hpp" #include "hwr2/pass_software.hpp" #include "hwr2/pass_twodee.hpp" #include "hwr2/twodee.hpp" @@ -118,6 +119,7 @@ static std::shared_ptr build_pass_manager() twodee->ctx_ = &g_2d; std::shared_ptr pp_simple_blit_pass = std::make_shared(false); std::shared_ptr pp_wipe_pass = std::make_shared(); + std::shared_ptr screenshot_pass = std::make_shared(); std::shared_ptr imgui_pass = std::make_shared(); std::shared_ptr final_composite_pass = std::make_shared(true); @@ -186,7 +188,7 @@ static std::shared_ptr build_pass_manager() } pp_simple_blit_pass->set_texture(color, vid.width, vid.height); pp_simple_blit_pass - ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true); + ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, false); } ); manager->insert("pp_final_simple_blit", pp_simple_blit_pass); @@ -209,12 +211,21 @@ static std::shared_ptr build_pass_manager() [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } ); + manager->insert( + "screenshot_prepare", + [screenshot_pass, framebuffer_manager](PassManager&, Rhi&) + { + screenshot_pass->set_source(framebuffer_manager->previous_post_color(), vid.width, vid.height); + } + ); + manager->insert("screenshot", screenshot_pass); + manager->insert( "final_composite_prepare", [final_composite_pass, framebuffer_manager](PassManager&, Rhi&) { final_composite_pass->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height); - final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, true); + final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, false); } ); manager->insert("final_composite", final_composite_pass); diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 60e5a2b65..f79edb7fb 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -1509,7 +1509,7 @@ void M_StopMovie(void) * \param palette Palette of image data. * \note if palette is NULL, BGR888 format */ -boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette) +boolean M_SavePNG(const char *filename, const void *data, int width, int height, const UINT8 *palette) { png_structp png_ptr; png_infop png_info_ptr; @@ -1580,7 +1580,7 @@ boolean M_SavePNG(const char *filename, void *data, int width, int height, const png_write_info(png_ptr, png_info_ptr); - M_PNGImage(png_ptr, png_info_ptr, height, static_cast(data)); + M_PNGImage(png_ptr, png_info_ptr, height, (png_bytep)data); png_write_end(png_ptr, png_info_ptr); png_destroy_write_struct(&png_ptr, &png_info_ptr); @@ -1688,19 +1688,24 @@ void M_ScreenShot(void) takescreenshot = true; } +void M_DoLegacyGLScreenShot(void) +{ + const std::byte* fake_data = nullptr; + M_DoScreenShot(vid.width, vid.height, tcb::span(fake_data, vid.width * vid.height)); +} + /** Takes a screenshot. * The screenshot is saved as "srb2xxxx.png" where xxxx is the lowest * four-digit number for which a file does not already exist. * * \sa HWR_ScreenShot */ -void M_DoScreenShot(void) +void M_DoScreenShot(UINT32 width, UINT32 height, tcb::span data) { #if NUMSCREENS > 2 const char *freename = NULL; char pathname[MAX_WADPATH]; boolean ret = false; - UINT8 *linear = NULL; // Don't take multiple screenshots, obviously takescreenshot = false; @@ -1733,13 +1738,6 @@ void M_DoScreenShot(void) freename = Newsnapshotfile(pathname,"tga"); #endif - if (rendermode == render_soft) - { - // munge planar buffer to linear - linear = screens[2]; - I_ReadScreen(linear); - } - if (!freename) goto failure; @@ -1750,9 +1748,9 @@ void M_DoScreenShot(void) else #endif { - M_CreateScreenShotPalette(); + const void* pixel_data = static_cast(data.data()); #ifdef USE_PNG - ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette); + ret = M_SavePNG(va(pandf,pathname,freename), pixel_data, width, height, NULL); #else ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette); #endif diff --git a/src/m_misc.h b/src/m_misc.h index 2df52fa96..2610d2f01 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -22,6 +22,13 @@ #include "command.h" #ifdef __cplusplus + +#include + +#include + +void M_DoScreenShot(uint32_t width, uint32_t height, tcb::span data); + extern "C" { #endif @@ -82,12 +89,14 @@ void FIL_ForceExtension(char *path, const char *extension); boolean FIL_CheckExtension(const char *in); #ifdef HAVE_PNG -boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette); +boolean M_SavePNG(const char *filename, const void *data, int width, int height, const UINT8 *palette); #endif extern boolean takescreenshot; void M_ScreenShot(void); -void M_DoScreenShot(void); +#ifdef HWRENDER +void M_DoLegacyGLScreenShot(void); +#endif boolean M_ScreenshotResponder(event_t *ev); void M_MinimapGenerate(void); diff --git a/src/rhi/gl3_core/gl3_core_rhi.cpp b/src/rhi/gl3_core/gl3_core_rhi.cpp index f98d25d0b..c4f1dd57b 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.cpp +++ b/src/rhi/gl3_core/gl3_core_rhi.cpp @@ -42,6 +42,12 @@ constexpr GLenum map_pixel_format(rhi::PixelFormat format) { switch (format) { + case rhi::PixelFormat::kR8: + return GL_R8; + case rhi::PixelFormat::kRG8: + return GL_RG8; + case rhi::PixelFormat::kRGB8: + return GL_RGB8; case rhi::PixelFormat::kRGBA8: return GL_RGBA8; case rhi::PixelFormat::kDepth16: @@ -70,6 +76,11 @@ constexpr std::tuple map_pixel_data_format(rhi::PixelFor type = GL_UNSIGNED_BYTE; size = 2; break; + case rhi::PixelFormat::kRGB8: + layout = GL_RGB; + type = GL_UNSIGNED_BYTE; + size = 3; + break; case rhi::PixelFormat::kRGBA8: layout = GL_RGBA; type = GL_UNSIGNED_BYTE; @@ -1581,13 +1592,19 @@ void GlCoreRhi::draw_indexed(Handle ctx, uint32_t index_count, GL_ASSERT } -void GlCoreRhi::read_pixels(Handle ctx, const Rect& rect, tcb::span out) +void GlCoreRhi::read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value()); - SRB2_ASSERT(out.size_bytes() == rect.w * rect.h); - gl_->ReadPixels(rect.x, rect.y, rect.w, rect.h, GL_RGBA, GL_UNSIGNED_BYTE, out.data()); + std::tuple gl_format = map_pixel_data_format(format); + GLenum layout = std::get<0>(gl_format); + GLenum type = std::get<1>(gl_format); + GLint size = std::get<2>(gl_format); + + SRB2_ASSERT(out.size_bytes() == rect.w * rect.h * size); + + gl_->ReadPixels(rect.x, rect.y, rect.w, rect.h, layout, type, out.data()); } void GlCoreRhi::finish() diff --git a/src/rhi/gl3_core/gl3_core_rhi.hpp b/src/rhi/gl3_core/gl3_core_rhi.hpp index b5b43afa1..548045463 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.hpp +++ b/src/rhi/gl3_core/gl3_core_rhi.hpp @@ -226,7 +226,8 @@ public: virtual void set_viewport(Handle ctx, const Rect& rect) override; virtual void draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) override; virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) override; - virtual void read_pixels(Handle ctx, const Rect& rect, tcb::span out) override; + virtual void + read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) override; virtual void present() override; diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index 43659fe40..2af19c118 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -73,6 +73,7 @@ enum class PixelFormat { kR8, kRG8, + kRGB8, kRGBA8, kDepth16, kStencil8 @@ -562,7 +563,8 @@ struct Rhi virtual void set_viewport(Handle ctx, const Rect& rect) = 0; virtual void draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) = 0; virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) = 0; - virtual void read_pixels(Handle ctx, const Rect& rect, tcb::span out) = 0; + virtual void + read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) = 0; virtual void present() = 0; From 0a9e9bbbaec7a899fd3d088708fc11eb098bf427 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 12 Feb 2023 11:46:00 -0600 Subject: [PATCH 09/60] hwr2: use palette manager in twodee --- src/hwr2/pass_twodee.cpp | 31 +++++-------------------------- src/hwr2/pass_twodee.hpp | 1 + src/i_video_common.cpp | 3 ++- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/hwr2/pass_twodee.cpp b/src/hwr2/pass_twodee.cpp index 469fdcd2a..da3e47176 100644 --- a/src/hwr2/pass_twodee.cpp +++ b/src/hwr2/pass_twodee.cpp @@ -58,7 +58,6 @@ struct Atlas struct srb2::hwr2::TwodeePassData { Handle default_tex; - Handle palette_tex; Handle default_colormap_tex; std::vector patch_atlases; std::unordered_map patch_lookup; @@ -482,10 +481,6 @@ void TwodeePass::prepass(Rhi& rhi) data_->default_tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, 2, 1}); data_->upload_default_tex = true; } - if (!data_->palette_tex) - { - data_->palette_tex = rhi.create_texture({TextureFormat::kRGBA, 256, 1}); - } if (!data_->default_colormap_tex) { data_->default_colormap_tex = rhi.create_texture({TextureFormat::kLuminance, 256, 1}); @@ -772,24 +767,6 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) data_->upload_default_tex = false; } - { - // TODO share palette tex with software pass - // Unfortunately, pMasterPalette must be swizzled to get a linear layout. - // Maybe some adjustments to palette storage can make this a straight upload. - std::array palette_32; - for (size_t i = 0; i < 256; i++) - { - palette_32[i] = pMasterPalette[i].s; - } - rhi.update_texture( - ctx, - data_->palette_tex, - {0, 0, 256, 1}, - rhi::PixelFormat::kRGBA8, - tcb::as_bytes(tcb::span(palette_32)) - ); - } - for (auto colormap : data_->colormaps_to_upload) { rhi.update_texture( @@ -821,6 +798,8 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) } data_->patches_to_upload.clear(); + Handle palette_tex = palette_manager_->palette(); + // Update the buffers for each list auto ctx_list_itr = ctx_->begin(); for (size_t i = 0; i < cmd_lists_.size() && ctx_list_itr != ctx_->end(); i++) @@ -843,14 +822,14 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) { Atlas& atlas = data_->patch_atlases[atlas_index]; tx[0] = {SamplerName::kSampler0, atlas.tex}; - tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + tx[1] = {SamplerName::kSampler1, palette_tex}; }, [&](const MergedTwodeeCommandFlatTexture& tex) { Handle th = flat_manager_->find_indexed(tex.lump); SRB2_ASSERT(th != kNullHandle); tx[0] = {SamplerName::kSampler0, th}; - tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + tx[1] = {SamplerName::kSampler1, palette_tex}; }}; if (mcmd.texture) { @@ -859,7 +838,7 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) else { tx[0] = {SamplerName::kSampler0, data_->default_tex}; - tx[1] = {SamplerName::kSampler1, data_->palette_tex}; + tx[1] = {SamplerName::kSampler1, palette_tex}; } const uint8_t* colormap = mcmd.colormap; diff --git a/src/hwr2/pass_twodee.hpp b/src/hwr2/pass_twodee.hpp index da9c2b563..4edb495c2 100644 --- a/src/hwr2/pass_twodee.hpp +++ b/src/hwr2/pass_twodee.hpp @@ -76,6 +76,7 @@ struct TwodeePass final : public Pass std::variant, rhi::Handle> out_color_; std::shared_ptr data_; + std::shared_ptr palette_manager_; std::shared_ptr flat_manager_; rhi::Handle us_1; rhi::Handle us_2; diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 844d86cdf..493faa821 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -158,9 +158,10 @@ static std::shared_ptr build_pass_manager() manager->insert( "2d_prepare", - [twodee, framebuffer_manager](PassManager& mgr, Rhi&) + [twodee, framebuffer_manager, palette_manager](PassManager& mgr, Rhi&) { twodee->output_ = framebuffer_manager->current_main_color(); + twodee->palette_manager_ = palette_manager; twodee->output_width_ = vid.width; twodee->output_height_ = vid.height; } From 31ce947659b23899271151ff2dd3192a3990e984 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Fri, 17 Feb 2023 21:19:35 -0600 Subject: [PATCH 10/60] hwr2: overhaul wipe rendering --- src/d_main.c | 6 + src/f_finale.h | 9 + src/f_wipe.c | 177 ++++++++++++- src/hwr2/pass_manager.cpp | 46 +++- src/hwr2/pass_manager.hpp | 13 +- src/hwr2/pass_postprocess.cpp | 41 ++- src/hwr2/pass_postprocess.hpp | 17 +- src/hwr2/pass_resource_managers.cpp | 58 +++++ src/hwr2/pass_resource_managers.hpp | 26 ++ src/i_video.h | 15 ++ src/i_video_common.cpp | 370 ++++++++++++++++++++-------- src/rhi/gl3_core/gl3_core_rhi.cpp | 8 + src/rhi/rhi.cpp | 7 +- src/rhi/rhi.hpp | 8 +- 14 files changed, 647 insertions(+), 154 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 229de62b7..58df759e4 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -800,6 +800,9 @@ void D_SRB2Loop(void) HW3S_BeginFrameUpdate(); #endif + I_NewTwodeeFrame(); + I_NewImguiFrame(); + if (realtics > 0 || singletics) { // don't skip more than 10 frames at a time @@ -1491,6 +1494,9 @@ void D_SRB2Main(void) CONS_Printf("I_StartupGraphics()...\n"); I_StartupGraphics(); + I_NewTwodeeFrame(); + I_NewImguiFrame(); + #ifdef HWRENDER // Lactozilla: Add every hardware mode CVAR and CCMD. // Has to be done before the configuration file loads, diff --git a/src/f_finale.h b/src/f_finale.h index e2d4599a2..970bf0619 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -144,6 +144,7 @@ extern UINT8 g_wipetype; extern UINT8 g_wipeframe; extern boolean g_wipereverse; extern boolean g_wipeskiprender; +extern boolean g_wipeencorewiggle; extern boolean WipeStageTitle; extern INT32 lastwipetic; @@ -159,6 +160,14 @@ void F_WipeStageTitle(void); #define F_WipeColorFill(c) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, c) tic_t F_GetWipeLength(UINT8 wipetype); boolean F_WipeExists(UINT8 wipetype); +/// @brief true if the wipetype is to-black +boolean F_WipeIsToBlack(UINT8 wipetype); +/// @brief true if the wipetype is to-white +boolean F_WipeIsToWhite(UINT8 wipetype); +/// @brief true if the wipetype is to-invert +boolean F_WipeIsToInvert(UINT8 wipetype); +/// @brief true if the wipetype is modulated from the previous frame +boolean F_WipeIsCrossfade(UINT8 wipetype); enum { diff --git a/src/f_wipe.c b/src/f_wipe.c index 98097b32c..435d79fda 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -85,6 +85,138 @@ UINT8 wipedefs[NUMWIPEDEFS] = { 99 // wipe_cutscene_final (hardcoded) }; +static boolean g_wipedef_toblack[NUMWIPEDEFS] = { + true, // wipe_credits_intermediate (0) + + true, // wipe_level_toblack + true, // wipe_intermission_toblack + true, // wipe_voting_toblack, + true, // wipe_continuing_toblack + true, // wipe_titlescreen_toblack + true, // wipe_menu_toblack + true, // wipe_credits_toblack + true, // wipe_evaluation_toblack + true, // wipe_gameend_toblack + true, // wipe_intro_toblack (hardcoded) + true, // wipe_ending_toblack (hardcoded) + true, // wipe_cutscene_toblack (hardcoded) + + false, // wipe_encore_toinvert + false, // wipe_encore_towhite + + true, // wipe_level_final + true, // wipe_intermission_final + true, // wipe_voting_final + true, // wipe_continuing_final + true, // wipe_titlescreen_final + true, // wipe_menu_final + true, // wipe_credits_final + true, // wipe_evaluation_final + true, // wipe_gameend_final + true, // wipe_intro_final (hardcoded) + true, // wipe_ending_final (hardcoded) + true // wipe_cutscene_final (hardcoded) +}; + +static boolean g_wipedef_toinvert[NUMWIPEDEFS] = { + false, // wipe_credits_intermediate (0) + + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack + false, // wipe_credits_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_intro_toblack (hardcoded) + false, // wipe_ending_toblack (hardcoded) + false, // wipe_cutscene_toblack (hardcoded) + + true, // wipe_encore_toinvert + false, // wipe_encore_towhite + + false, // wipe_level_final + false, // wipe_intermission_final + false, // wipe_voting_final + false, // wipe_continuing_final + false, // wipe_titlescreen_final + false, // wipe_menu_final + false, // wipe_credits_final + false, // wipe_evaluation_final + false, // wipe_gameend_final + false, // wipe_intro_final (hardcoded) + false, // wipe_ending_final (hardcoded) + false // wipe_cutscene_final (hardcoded) +}; + +static boolean g_wipedef_towhite[NUMWIPEDEFS] = { + false, // wipe_credits_intermediate (0) + + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack + false, // wipe_credits_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_intro_toblack (hardcoded) + false, // wipe_ending_toblack (hardcoded) + false, // wipe_cutscene_toblack (hardcoded) + + false, // wipe_encore_toinvert + true, // wipe_encore_towhite + + false, // wipe_level_final + false, // wipe_intermission_final + false, // wipe_voting_final + false, // wipe_continuing_final + false, // wipe_titlescreen_final + false, // wipe_menu_final + false, // wipe_credits_final + false, // wipe_evaluation_final + false, // wipe_gameend_final + false, // wipe_intro_final (hardcoded) + false, // wipe_ending_final (hardcoded) + false // wipe_cutscene_final (hardcoded) +}; + +static boolean g_wipedef_crossfade[NUMWIPEDEFS] = { + false, // wipe_credits_intermediate (0) + + false, // wipe_level_toblack + false, // wipe_intermission_toblack + false, // wipe_voting_toblack, + false, // wipe_continuing_toblack + false, // wipe_titlescreen_toblack + false, // wipe_menu_toblack + false, // wipe_credits_toblack + false, // wipe_evaluation_toblack + false, // wipe_gameend_toblack + false, // wipe_intro_toblack (hardcoded) + false, // wipe_ending_toblack (hardcoded) + false, // wipe_cutscene_toblack (hardcoded) + + false, // wipe_encore_toinvert + false, // wipe_encore_towhite + + true, // wipe_level_final + true, // wipe_intermission_final + true, // wipe_voting_final + true, // wipe_continuing_final + true, // wipe_titlescreen_final + true, // wipe_menu_final + true, // wipe_credits_final + true, // wipe_evaluation_final + true, // wipe_gameend_final + true, // wipe_intro_final (hardcoded) + true, // wipe_ending_final (hardcoded) + true // wipe_cutscene_final (hardcoded) +}; + //-------------------------------------------------------------------------- // SCREEN WIPE PACKAGE //-------------------------------------------------------------------------- @@ -94,6 +226,7 @@ UINT8 g_wipetype = 0; UINT8 g_wipeframe = 0; boolean g_wipereverse = false; boolean g_wipeskiprender = false; +boolean g_wipeencorewiggle = false; boolean WipeStageTitle = false; INT32 lastwipetic = 0; @@ -207,8 +340,9 @@ void F_WipeStartScreen(void) return; } #endif - wipe_scr_start = screens[3]; - I_ReadScreen(wipe_scr_start); + // wipe_scr_start = screens[3]; + // I_ReadScreen(wipe_scr_start); + I_FinishUpdateWipeStartScreen(); #endif } @@ -224,9 +358,10 @@ void F_WipeEndScreen(void) return; } #endif - wipe_scr_end = screens[4]; - I_ReadScreen(wipe_scr_end); - V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start); + // wipe_scr_end = screens[4]; + // I_ReadScreen(wipe_scr_end); + // V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start); + I_FinishUpdateWipeEndScreen(); #endif } @@ -360,10 +495,11 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r if (encorewiggle) { -#ifdef HWRENDER - if (rendermode != render_opengl) -#endif - F_DoEncoreWiggle(wipeframe); + g_wipeencorewiggle = wipeframe - 1; + } + else + { + g_wipeencorewiggle = 0; } } @@ -381,7 +517,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r #endif } - I_FinishUpdate(); // page flip or blit buffer + I_FinishUpdateWipe(); // page flip or blit buffer if (rendermode != render_none) { @@ -454,3 +590,24 @@ boolean F_WipeExists(UINT8 wipetype) return !(lumpnum == LUMPERROR); #endif } + +boolean F_WipeIsToBlack(UINT8 wipetype) +{ + return g_wipedef_toblack[wipetype]; +} + +boolean F_WipeIsToWhite(UINT8 wipetype) +{ + return g_wipedef_towhite[wipetype]; +} + +boolean F_WipeIsToInvert(UINT8 wipetype) +{ + return g_wipedef_toinvert[wipetype]; +} + +boolean F_WipeIsCrossfade(UINT8 wipetype) +{ + return g_wipedef_crossfade[wipetype]; +} + diff --git a/src/hwr2/pass_manager.cpp b/src/hwr2/pass_manager.cpp index e79ef2725..a6bc386a7 100644 --- a/src/hwr2/pass_manager.cpp +++ b/src/hwr2/pass_manager.cpp @@ -80,6 +80,8 @@ void LambdaPass::postpass(Rhi& rhi) } PassManager::PassManager() = default; +PassManager::PassManager(const PassManager&) = default; +PassManager& PassManager::operator=(const PassManager&) = default; void PassManager::insert(const std::string& name, std::shared_ptr pass) { @@ -124,13 +126,8 @@ std::weak_ptr PassManager::for_name(const std::string& name) return passes_[itr->second].pass; } -void PassManager::render(Rhi& rhi) const +void PassManager::prepass(Rhi& rhi) { - if (passes_.empty()) - { - return; - } - for (auto& pass : passes_) { if (pass.enabled) @@ -138,27 +135,32 @@ void PassManager::render(Rhi& rhi) const pass.pass->prepass(rhi); } } +} - Handle tc = rhi.begin_transfer(); +void PassManager::transfer(Rhi& rhi, Handle ctx) +{ for (auto& pass : passes_) { if (pass.enabled) { - pass.pass->transfer(rhi, tc); + pass.pass->transfer(rhi, ctx); } } - rhi.end_transfer(tc); +} - Handle gc = rhi.begin_graphics(); +void PassManager::graphics(Rhi& rhi, Handle ctx) +{ for (auto& pass : passes_) { if (pass.enabled) { - pass.pass->graphics(rhi, gc); + pass.pass->graphics(rhi, ctx); } } - rhi.end_graphics(gc); +} +void PassManager::postpass(Rhi& rhi) +{ for (auto& pass : passes_) { if (pass.enabled) @@ -167,3 +169,23 @@ void PassManager::render(Rhi& rhi) const } } } + +void PassManager::render(Rhi& rhi) +{ + if (passes_.empty()) + { + return; + } + + prepass(rhi); + + Handle tc = rhi.begin_transfer(); + transfer(rhi, tc); + rhi.end_transfer(tc); + + Handle gc = rhi.begin_graphics(); + graphics(rhi, gc); + rhi.end_graphics(gc); + + postpass(rhi); +} diff --git a/src/hwr2/pass_manager.hpp b/src/hwr2/pass_manager.hpp index 372db893e..c462dd395 100644 --- a/src/hwr2/pass_manager.hpp +++ b/src/hwr2/pass_manager.hpp @@ -23,7 +23,7 @@ namespace srb2::hwr2 { -class PassManager +class PassManager final : public Pass { struct PassManagerEntry { @@ -37,11 +37,16 @@ class PassManager public: PassManager(); - PassManager(const PassManager&) = delete; + PassManager(const PassManager&); PassManager(PassManager&&) = delete; - PassManager& operator=(const PassManager&) = delete; + PassManager& operator=(const PassManager&); PassManager& operator=(PassManager&&) = delete; + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + void insert(const std::string& name, std::shared_ptr pass); void insert(const std::string& name, std::function prepass_func); void insert( @@ -52,7 +57,7 @@ public: std::weak_ptr for_name(const std::string& name); void set_pass_enabled(const std::string& name, bool enabled); - void render(rhi::Rhi& rhi) const; + void render(rhi::Rhi& rhi); }; } // namespace srb2::hwr2 diff --git a/src/hwr2/pass_postprocess.cpp b/src/hwr2/pass_postprocess.cpp index 89b1ae005..333338159 100644 --- a/src/hwr2/pass_postprocess.cpp +++ b/src/hwr2/pass_postprocess.cpp @@ -46,8 +46,8 @@ static const PipelineDesc kWipePipelineDesc = { {VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}, }}, - {{{{UniformName::kProjection}}}}, - {{SamplerName::kSampler0, SamplerName::kSampler1}}, + {{{{UniformName::kProjection, UniformName::kWipeColorizeMode, UniformName::kWipeEncoreSwizzle}}}}, + {{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}}, std::nullopt, {PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}}, PrimitiveType::kTriangles, @@ -55,7 +55,7 @@ static const PipelineDesc kWipePipelineDesc = { FaceWinding::kCounterClockwise, {0.f, 0.f, 0.f, 1.f}}; -PostprocessWipePass::PostprocessWipePass() : Pass() +PostprocessWipePass::PostprocessWipePass() { } @@ -89,6 +89,27 @@ void PostprocessWipePass::prepass(Rhi& rhi) uint32_t wipe_type = g_wipetype; uint32_t wipe_frame = g_wipeframe; bool wipe_reverse = g_wipereverse; + + wipe_color_mode_ = 0; // TODO 0 = modulate, 1 = invert, 2 = MD to black, 3 = MD to white + if (F_WipeIsToBlack(wipe_type)) + { + wipe_color_mode_ = 2; + } + else if (F_WipeIsToWhite(wipe_type)) + { + wipe_color_mode_ = 3; + } + else if (F_WipeIsToInvert(wipe_type)) + { + wipe_color_mode_ = 1; + } + else if (F_WipeIsCrossfade(wipe_type)) + { + wipe_color_mode_ = 0; + } + + wipe_swizzle_ = g_wipeencorewiggle; + if (wipe_type >= 100 || wipe_frame >= 100) { return; @@ -145,7 +166,7 @@ void PostprocessWipePass::transfer(Rhi& rhi, Handle ctx) return; } - if (source_ == kNullHandle) + if (start_ == kNullHandle || end_ == kNullHandle) { return; } @@ -166,13 +187,17 @@ void PostprocessWipePass::transfer(Rhi& rhi, Handle ctx) rhi.update_texture(ctx, wipe_tex_, {0, 0, mask_w_, mask_h_}, PixelFormat::kR8, data); UniformVariant uniforms[] = { - {// Projection - std::array, 4> { - {{2.f, 0.f, 0.f, 0.f}, {0.f, 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}}}}; + {std::array, 4> { + {{2.f, 0.f, 0.f, 0.f}, {0.f, 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}}}, + {static_cast(wipe_color_mode_)}, + {static_cast(wipe_swizzle_)}}; us_ = rhi.create_uniform_set(ctx, {tcb::span(uniforms)}); VertexAttributeBufferBinding vbos[] = {{0, vbo_}}; - TextureBinding tx[] = {{SamplerName::kSampler0, source_}, {SamplerName::kSampler1, wipe_tex_}}; + TextureBinding tx[] = { + {SamplerName::kSampler0, start_}, + {SamplerName::kSampler1, end_}, + {SamplerName::kSampler2, wipe_tex_}}; bs_ = rhi.create_binding_set(ctx, pipeline_, {vbos, tx}); } diff --git a/src/hwr2/pass_postprocess.hpp b/src/hwr2/pass_postprocess.hpp index 113d96296..ced81975c 100644 --- a/src/hwr2/pass_postprocess.hpp +++ b/src/hwr2/pass_postprocess.hpp @@ -19,6 +19,7 @@ namespace srb2::hwr2 class PostprocessWipePass final : public Pass { + // Internal RHI resources rhi::Handle render_pass_; rhi::Handle pipeline_; rhi::Handle vbo_; @@ -28,14 +29,17 @@ class PostprocessWipePass final : public Pass rhi::Handle us_; rhi::Handle bs_; rhi::Handle wipe_tex_; - rhi::Handle source_; - uint32_t source_w_ = 0; - uint32_t source_h_ = 0; + int wipe_color_mode_ = 0; + int wipe_swizzle_ = 0; + + // Pass parameters + rhi::Handle start_; rhi::Handle end_; rhi::Handle target_; uint32_t target_w_ = 0; uint32_t target_h_ = 0; + // Mask lump loading std::vector mask_data_; uint32_t mask_w_ = 0; uint32_t mask_h_ = 0; @@ -49,12 +53,7 @@ public: virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; virtual void postpass(rhi::Rhi& rhi) override; - void set_source(rhi::Handle source, uint32_t width, uint32_t height) noexcept - { - source_ = source; - source_w_ = width; - source_h_ = height; - } + void set_start(rhi::Handle start) noexcept { start_ = start; } void set_end(rhi::Handle end) noexcept { end_ = end; } diff --git a/src/hwr2/pass_resource_managers.cpp b/src/hwr2/pass_resource_managers.cpp index 2f5e599c4..40efb0980 100644 --- a/src/hwr2/pass_resource_managers.cpp +++ b/src/hwr2/pass_resource_managers.cpp @@ -59,6 +59,16 @@ void FramebufferManager::prepass(Rhi& rhi) rhi.destroy_texture(post_colors_[1]); post_colors_[1] = kNullHandle; } + if (wipe_start_color_ != kNullHandle) + { + rhi.destroy_texture(wipe_start_color_); + wipe_start_color_ = kNullHandle; + } + if (wipe_end_color_ != kNullHandle) + { + rhi.destroy_texture(wipe_end_color_); + wipe_end_color_ = kNullHandle; + } } width_ = current_width; height_ = current_height; @@ -85,6 +95,15 @@ void FramebufferManager::prepass(Rhi& rhi) { post_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); } + + if (wipe_start_color_ == kNullHandle) + { + wipe_start_color_ = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } + if (wipe_end_color_ == kNullHandle) + { + wipe_end_color_ = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + } } void FramebufferManager::transfer(Rhi& rhi, Handle ctx) @@ -131,6 +150,45 @@ void MainPaletteManager::postpass(Rhi& rhi) { } +CommonResourcesManager::CommonResourcesManager() = default; +CommonResourcesManager::~CommonResourcesManager() = default; + +void CommonResourcesManager::prepass(Rhi& rhi) +{ + if (!init_) + { + black_ = rhi.create_texture({TextureFormat::kRGBA, 1, 1}); + white_ = rhi.create_texture({TextureFormat::kRGBA, 1, 1}); + transparent_ = rhi.create_texture({TextureFormat::kRGBA, 1, 1}); + } +} + +void CommonResourcesManager::transfer(Rhi& rhi, Handle ctx) +{ + if (!init_) + { + uint8_t black[4] = {0, 0, 0, 255}; + tcb::span black_bytes = tcb::as_bytes(tcb::span(black, 4)); + uint8_t white[4] = {255, 255, 255, 255}; + tcb::span white_bytes = tcb::as_bytes(tcb::span(white, 4)); + uint8_t transparent[4] = {0, 0, 0, 0}; + tcb::span transparent_bytes = tcb::as_bytes(tcb::span(transparent, 4)); + + rhi.update_texture(ctx, black_, {0, 0, 1, 1}, PixelFormat::kRGBA8, black_bytes); + rhi.update_texture(ctx, white_, {0, 0, 1, 1}, PixelFormat::kRGBA8, white_bytes); + rhi.update_texture(ctx, transparent_, {0, 0, 1, 1}, PixelFormat::kRGBA8, transparent_bytes); + } +} + +void CommonResourcesManager::graphics(Rhi& rhi, Handle ctx) +{ +} + +void CommonResourcesManager::postpass(Rhi& rhi) +{ + init_ = true; +} + static uint32_t get_flat_size(lumpnum_t lump) { SRB2_ASSERT(lump != LUMPERROR); diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp index 4eee36b8c..43e9dc56f 100644 --- a/src/hwr2/pass_resource_managers.hpp +++ b/src/hwr2/pass_resource_managers.hpp @@ -25,6 +25,8 @@ class FramebufferManager final : public Pass std::array, 2> main_colors_; rhi::Handle main_depth_; std::array, 2> post_colors_; + rhi::Handle wipe_start_color_; + rhi::Handle wipe_end_color_; std::size_t main_index_ = 0; std::size_t post_index_ = 0; std::size_t width_ = 0; @@ -67,6 +69,9 @@ public: return post_colors_[1 - post_index_]; }; + rhi::Handle wipe_start_color() const noexcept { return wipe_start_color_; } + rhi::Handle wipe_end_color() const noexcept { return wipe_end_color_; } + std::size_t width() const noexcept { return width_; } std::size_t height() const noexcept { return height_; } }; @@ -87,6 +92,27 @@ public: rhi::Handle palette() const noexcept { return palette_; } }; +class CommonResourcesManager final : public Pass +{ + bool init_ = false; + rhi::Handle black_; + rhi::Handle white_; + rhi::Handle transparent_; + +public: + CommonResourcesManager(); + virtual ~CommonResourcesManager(); + + virtual void prepass(rhi::Rhi& rhi) override; + virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; + virtual void postpass(rhi::Rhi& rhi) override; + + rhi::Handle black() const noexcept { return black_; } + rhi::Handle white() const noexcept { return white_; } + rhi::Handle transparent() const noexcept { return transparent_; } +}; + /* A note to the reader: diff --git a/src/i_video.h b/src/i_video.h index a80bd33dc..4da72efc0 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -130,10 +130,25 @@ extern boolean allow_fullscreen; */ void I_UpdateNoBlit(void); +/** \brief Begin a new Twodee frame. +*/ +void I_NewTwodeeFrame(void); + +/** \brief Begin a new dear imgui frame. +*/ +void I_NewImguiFrame(void); + /** \brief Update video system with updating frame */ void I_FinishUpdate(void); +void I_FinishUpdateWipeStartScreen(void); +void I_FinishUpdateWipeEndScreen(void); + +/** \brief Update video system during a wipe +*/ +void I_FinishUpdateWipe(void); + /** \brief I_FinishUpdate(), but vsync disabled */ void I_UpdateNoVsync(void); diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 493faa821..f3371a344 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -40,13 +40,26 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -static std::shared_ptr g_passmanager; +namespace +{ +struct InternalPassData +{ + std::shared_ptr resource_passmanager; + std::shared_ptr normal_rendering; + std::shared_ptr wipe_capture_start_rendering; + std::shared_ptr wipe_capture_end_rendering; + std::shared_ptr wipe_rendering; +}; +} // namespace + +static std::unique_ptr g_passes; +static Rhi* g_last_known_rhi = nullptr; Handle srb2::sys::g_current_rhi = kNullHandle; -static bool rhi_changed() +static bool rhi_changed(Rhi* rhi) { - return false; + return g_last_known_rhi != rhi; } #ifdef HWRENDER @@ -103,50 +116,49 @@ static void finish_legacy_ogl_update() } #endif -static std::shared_ptr build_pass_manager() +static InternalPassData build_pass_manager() { - std::shared_ptr manager = std::make_shared(); + auto framebuffer_manager = std::make_shared(); + auto palette_manager = std::make_shared(); + auto common_resources_manager = std::make_shared(); + auto flat_texture_manager = std::make_shared(); + auto resource_manager = std::make_shared(); - std::shared_ptr framebuffer_manager = std::make_shared(); - std::shared_ptr palette_manager = std::make_shared(); - std::shared_ptr flat_texture_manager = std::make_shared(); + resource_manager->insert("framebuffer_manager", framebuffer_manager); + resource_manager->insert("palette_manager", palette_manager); + resource_manager->insert("common_resources_manager", common_resources_manager); + resource_manager->insert("flat_texture_manager", flat_texture_manager); - std::shared_ptr software_pass = std::make_shared(); - std::shared_ptr blit_sw_pass = std::make_shared(palette_manager, true); - std::shared_ptr twodee = std::make_shared(); + // Basic Rendering is responsible for drawing 3d, 2d, and postprocessing the image. + // This is drawn to an alternating internal color buffer. + // Normal Rendering will output the result via final composite and present. + // Wipe Start Screen and Wipe End Screen will save to special color buffers used for Wipe Rendering. + auto basic_rendering = std::make_shared(); + + auto software_pass = std::make_shared(); + auto blit_sw_pass = std::make_shared(palette_manager, true); + auto twodee = std::make_shared(); twodee->flat_manager_ = flat_texture_manager; twodee->data_ = make_twodee_pass_data(); twodee->ctx_ = &g_2d; - std::shared_ptr pp_simple_blit_pass = std::make_shared(false); - std::shared_ptr pp_wipe_pass = std::make_shared(); - std::shared_ptr screenshot_pass = std::make_shared(); - std::shared_ptr imgui_pass = std::make_shared(); - std::shared_ptr final_composite_pass = std::make_shared(true); + auto pp_simple_blit_pass = std::make_shared(false); + auto screenshot_pass = std::make_shared(); + auto imgui_pass = std::make_shared(); + auto final_composite_pass = std::make_shared(true); - manager->insert("framebuffer_manager", framebuffer_manager); - manager->insert("palette_manager", palette_manager); - manager->insert("flat_texture_manager", flat_texture_manager); - - manager->insert( + basic_rendering->insert( "3d_prepare", [framebuffer_manager](PassManager& mgr, Rhi&) { const bool sw_enabled = rendermode == render_soft; - mgr.set_pass_enabled("software", !g_wipeskiprender && sw_enabled); - mgr.set_pass_enabled("blit_sw_prepare", !g_wipeskiprender && sw_enabled); - mgr.set_pass_enabled("blit_sw", !g_wipeskiprender && sw_enabled); - }, - [framebuffer_manager](PassManager&, Rhi&) - { - if (!WipeInAction) - { - framebuffer_manager->swap_main(); - } + mgr.set_pass_enabled("software", sw_enabled); + mgr.set_pass_enabled("blit_sw_prepare", sw_enabled); + mgr.set_pass_enabled("blit_sw", sw_enabled); } ); - manager->insert("software", software_pass); - manager->insert( + basic_rendering->insert("software", software_pass); + basic_rendering->insert( "blit_sw_prepare", [blit_sw_pass, software_pass, framebuffer_manager](PassManager&, Rhi&) { @@ -154,9 +166,9 @@ static std::shared_ptr build_pass_manager() blit_sw_pass->set_output(framebuffer_manager->current_main_color(), vid.width, vid.height, false, false); } ); - manager->insert("blit_sw", blit_sw_pass); + basic_rendering->insert("blit_sw", blit_sw_pass); - manager->insert( + basic_rendering->insert( "2d_prepare", [twodee, framebuffer_manager, palette_manager](PassManager& mgr, Rhi&) { @@ -166,74 +178,62 @@ static std::shared_ptr build_pass_manager() twodee->output_height_ = vid.height; } ); - manager->insert("2d", twodee); + basic_rendering->insert("2d", twodee); - manager->insert( - "pp_final_prepare", - [](PassManager& mgr, Rhi&) - { - mgr.set_pass_enabled("pp_final_wipe_prepare", WipeInAction); - mgr.set_pass_enabled("pp_final_wipe", WipeInAction); - mgr.set_pass_enabled("pp_final_wipe_flip", WipeInAction); - } - ); - manager->insert( + // basic_rendering->insert( + // "pp_final_prepare", + // [](PassManager& mgr, Rhi&) + // { + // mgr.set_pass_enabled("pp_final_wipe_prepare", WipeInAction); + // mgr.set_pass_enabled("pp_final_wipe", WipeInAction); + // mgr.set_pass_enabled("pp_final_wipe_flip", WipeInAction); + // } + // ); + basic_rendering->insert( "pp_final_simple_blit_prepare", [pp_simple_blit_pass, framebuffer_manager](PassManager&, Rhi&) { - Handle color = framebuffer_manager->current_main_color(); - if (WipeInAction && !g_wipereverse) - { - // Non-reverse wipes are "fade-outs" from the previous frame. - color = framebuffer_manager->previous_main_color(); - } - pp_simple_blit_pass->set_texture(color, vid.width, vid.height); + framebuffer_manager->swap_post(); + pp_simple_blit_pass->set_texture(framebuffer_manager->current_main_color(), vid.width, vid.height); pp_simple_blit_pass - ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, false); + ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true); } ); - manager->insert("pp_final_simple_blit", pp_simple_blit_pass); - manager->insert( - "pp_final_simple_blit_flip", - [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } - ); - manager->insert( - "pp_final_wipe_prepare", - [pp_wipe_pass, framebuffer_manager](PassManager&, Rhi&) - { - pp_wipe_pass->set_source(framebuffer_manager->previous_post_color(), vid.width, vid.height); - pp_wipe_pass->set_end(framebuffer_manager->current_main_color()); - pp_wipe_pass->set_target(framebuffer_manager->current_post_color(), vid.width, vid.height); - } - ); - manager->insert("pp_final_wipe", pp_wipe_pass); - manager->insert( - "pp_final_wipe_flip", - [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } - ); + basic_rendering->insert("pp_final_simple_blit", pp_simple_blit_pass); + // basic_rendering->insert( + // "pp_final_simple_blit_flip", + // [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } + // ); - manager->insert( + // basic_rendering->insert( + // "pp_final_wipe_flip", + // [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } + // ); + + basic_rendering->insert( "screenshot_prepare", [screenshot_pass, framebuffer_manager](PassManager&, Rhi&) { - screenshot_pass->set_source(framebuffer_manager->previous_post_color(), vid.width, vid.height); + screenshot_pass->set_source(framebuffer_manager->current_post_color(), vid.width, vid.height); } ); - manager->insert("screenshot", screenshot_pass); + basic_rendering->insert("screenshot", screenshot_pass); - manager->insert( + // Composite-present takes the current postprocess result and outputs it to the default framebuffer. + // It also renders imgui and presents the screen. + auto composite_present_rendering = std::make_shared(); + + composite_present_rendering->insert( "final_composite_prepare", [final_composite_pass, framebuffer_manager](PassManager&, Rhi&) { - final_composite_pass->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height); - final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, false); + final_composite_pass->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); + final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, true); } ); - manager->insert("final_composite", final_composite_pass); - - manager->insert("imgui", imgui_pass); - - manager->insert( + composite_present_rendering->insert("final_composite", final_composite_pass); + composite_present_rendering->insert("imgui", imgui_pass); + composite_present_rendering->insert( "present", [](PassManager&, Rhi& rhi) {}, [framebuffer_manager](PassManager&, Rhi& rhi) @@ -241,25 +241,104 @@ static std::shared_ptr build_pass_manager() rhi.present(); rhi.finish(); framebuffer_manager->reset_post(); - - // TODO fix this: it's an ugly hack to work around issues with wipes - // Why this works: - // - Menus run F_RunWipe which is an inner update loop calling I_FinishUpdate, with this global set - // - After exiting F_RunWipe, g_2d should normally be cleared by I_FinishUpdate - // - Unfortunately, the menu has already run all its draw calls when exiting F_RunWipe - // - That causes a single-frame flash of no 2d content, which is an epilepsy risk. - // - By not clearing the 2d context, we are redrawing 2d every frame of the wipe - // - This "works" because we draw 2d to the normal color buffer, not the postprocessed screen. - // - It does result in the FPS counter being mangled during the wipe though. - // - To fix the issues around wipes, wipes need to be a "sub" game state, and eliminate the inner tic loops. - if (!WipeInAction) - { - g_2d = Twodee(); - } } ); - return manager; + // Normal rendering combines basic rendering and composite-present. + auto normal_rendering = std::make_shared(); + + normal_rendering->insert("resource_manager", resource_manager); + normal_rendering->insert("basic_rendering", basic_rendering); + normal_rendering->insert("composite_present_rendering", composite_present_rendering); + + // Wipe Start Screen Capture rendering + auto wipe_capture_start_rendering = std::make_shared(); + auto wipe_start_blit = std::make_shared(); + + wipe_capture_start_rendering->insert("resource_manager", resource_manager); + wipe_capture_start_rendering->insert("basic_rendering", basic_rendering); + wipe_capture_start_rendering->insert( + "wipe_capture_prepare", + [framebuffer_manager, wipe_start_blit](PassManager&, Rhi&) + { + wipe_start_blit->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height); + wipe_start_blit->set_output(framebuffer_manager->wipe_start_color(), vid.width, vid.height, false, true); + } + ); + wipe_capture_start_rendering->insert("wipe_capture", wipe_start_blit); + + // Wipe End Screen Capture rendering + auto wipe_capture_end_rendering = std::make_shared(); + auto wipe_end_blit = std::make_shared(); + + wipe_capture_end_rendering->insert("resource_manager", resource_manager); + wipe_capture_end_rendering->insert("basic_rendering", basic_rendering); + wipe_capture_end_rendering->insert( + "wipe_capture_prepare", + [framebuffer_manager, wipe_end_blit](PassManager&, Rhi&) + { + wipe_end_blit->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); + wipe_end_blit->set_output(framebuffer_manager->wipe_end_color(), vid.width, vid.height, false, true); + } + ); + wipe_capture_end_rendering->insert("wipe_capture", wipe_end_blit); + + // Wipe rendering only runs the wipe shader on the start and end screens, and adds composite-present. + auto wipe_rendering = std::make_shared(); + + auto pp_wipe_pass = std::make_shared(); + + wipe_rendering->insert("resource_manager", resource_manager); + wipe_rendering->insert( + "pp_final_wipe_prepare", + [pp_wipe_pass, framebuffer_manager, common_resources_manager](PassManager&, Rhi&) + { + framebuffer_manager->swap_post(); + Handle start = framebuffer_manager->wipe_start_color(); + Handle end = framebuffer_manager->wipe_end_color(); + if (g_wipereverse) + { + std::swap(start, end); + } + pp_wipe_pass->set_start(start); + pp_wipe_pass->set_end(end); + pp_wipe_pass->set_target(framebuffer_manager->current_post_color(), vid.width, vid.height); + } + ); + wipe_rendering->insert("pp_final_wipe", pp_wipe_pass); + wipe_rendering->insert("composite_present_rendering", composite_present_rendering); + + InternalPassData ret; + ret.resource_passmanager = resource_manager; + ret.normal_rendering = normal_rendering; + ret.wipe_capture_start_rendering = wipe_capture_start_rendering; + ret.wipe_capture_end_rendering = wipe_capture_end_rendering; + ret.wipe_rendering = wipe_rendering; + + return ret; +} + +void I_NewTwodeeFrame(void) +{ + g_2d = Twodee(); +} + +void I_NewImguiFrame(void) +{ + // TODO move this to srb2loop + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = vid.realwidth; + io.DisplaySize.y = vid.realheight; + ImGui::NewFrame(); +} + +static void maybe_reinit_passes(Rhi* rhi) +{ + if (rhi_changed(rhi) || !g_passes) + { + g_last_known_rhi = rhi; + g_passes = std::make_unique(build_pass_manager()); + } } void I_FinishUpdate(void) @@ -277,17 +356,34 @@ void I_FinishUpdate(void) } #endif - // TODO move this to srb2loop - ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize.x = vid.realwidth; - io.DisplaySize.y = vid.realheight; - ImGui::NewFrame(); + rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); - if (rhi_changed() || !g_passmanager) + if (rhi == nullptr) { - g_passmanager = build_pass_manager(); + // ??? + return; } + maybe_reinit_passes(rhi); + + g_passes->normal_rendering->render(*rhi); +} + +void I_FinishUpdateWipeStartScreen(void) +{ + if (rendermode == render_none) + { + return; + } + +#ifdef HWRENDER + if (rendermode == render_opengl) + { + finish_legacy_ogl_update(); + return; + } +#endif + rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); if (rhi == nullptr) @@ -296,5 +392,63 @@ void I_FinishUpdate(void) return; } - g_passmanager->render(*rhi); + maybe_reinit_passes(rhi); + + g_passes->wipe_capture_start_rendering->render(*rhi); +} + +void I_FinishUpdateWipeEndScreen(void) +{ + if (rendermode == render_none) + { + return; + } + +#ifdef HWRENDER + if (rendermode == render_opengl) + { + finish_legacy_ogl_update(); + return; + } +#endif + + rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); + + if (rhi == nullptr) + { + // ??? + return; + } + + maybe_reinit_passes(rhi); + + g_passes->wipe_capture_end_rendering->render(*rhi); +} + +void I_FinishUpdateWipe(void) +{ + if (rendermode == render_none) + { + return; + } + +#ifdef HWRENDER + if (rendermode == render_opengl) + { + finish_legacy_ogl_update(); + return; + } +#endif + + rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); + + if (rhi == nullptr) + { + // ??? + return; + } + + maybe_reinit_passes(rhi); + + g_passes->wipe_rendering->render(*rhi); } diff --git a/src/rhi/gl3_core/gl3_core_rhi.cpp b/src/rhi/gl3_core/gl3_core_rhi.cpp index c4f1dd57b..856913d53 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.cpp +++ b/src/rhi/gl3_core/gl3_core_rhi.cpp @@ -334,6 +334,10 @@ constexpr const char* map_uniform_attribute_symbol_name(rhi::UniformName name) return "u_texcoord0_transform"; case rhi::UniformName::kSampler0IsIndexedAlpha: return "u_sampler0_is_indexed_alpha"; + case rhi::UniformName::kWipeColorizeMode: + return "u_wipe_colorize_mode"; + case rhi::UniformName::kWipeEncoreSwizzle: + return "u_wipe_encore_swizzle"; default: return nullptr; } @@ -353,6 +357,10 @@ constexpr const char* map_uniform_enable_define(rhi::UniformName name) return "ENABLE_U_TEXCOORD0_TRANSFORM"; case rhi::UniformName::kSampler0IsIndexedAlpha: return "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA"; + case rhi::UniformName::kWipeColorizeMode: + return "ENABLE_U_WIPE_COLORIZE_MODE"; + case rhi::UniformName::kWipeEncoreSwizzle: + return "ENABLE_U_WIPE_ENCORE_SWIZZLE"; default: return nullptr; } diff --git a/src/rhi/rhi.cpp b/src/rhi/rhi.cpp index 7e166246a..36c3c7c8e 100644 --- a/src/rhi/rhi.cpp +++ b/src/rhi/rhi.cpp @@ -44,8 +44,11 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsPostprocessWipe = { ProgramVertexInputRequirements { {ProgramVertexInput {VertexAttributeName::kPosition, VertexAttributeFormat::kFloat3, true}, ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, true}}}, - ProgramUniformRequirements {{{{{UniformName::kProjection, true}, {UniformName::kModelView, true}}}}}, - ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}}}}; + ProgramUniformRequirements { + {{{{UniformName::kProjection, true}, + {UniformName::kWipeColorizeMode, true}, + {UniformName::kWipeEncoreSwizzle, true}}}}}, + ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}, {SamplerName::kSampler2, true}}}}; const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram program) noexcept { diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index 2af19c118..a6186ba97 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -196,7 +196,9 @@ enum class UniformName kModelView, kProjection, kTexCoord0Transform, - kSampler0IsIndexedAlpha + kSampler0IsIndexedAlpha, + kWipeColorizeMode, + kWipeEncoreSwizzle }; enum class SamplerName @@ -306,6 +308,10 @@ inline constexpr const UniformFormat uniform_format(UniformName name) noexcept return UniformFormat::kMat3; case UniformName::kSampler0IsIndexedAlpha: return UniformFormat::kInt; + case UniformName::kWipeColorizeMode: + return UniformFormat::kInt; + case UniformName::kWipeEncoreSwizzle: + return UniformFormat::kInt; default: return UniformFormat::kFloat; } From 158aaa68037941caaa178e978be4c502bc54e1bf Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 20 Feb 2023 17:48:55 -0600 Subject: [PATCH 11/60] hwr2: fix DrawFill full screen special case --- src/r_main.c | 12 +++++++++--- src/v_video.cpp | 22 ++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/r_main.c b/src/r_main.c index 2c781509c..b00717810 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1476,9 +1476,15 @@ void R_RenderPlayerView(void) if (cv_homremoval.value && player == &players[displayplayers[0]]) { if (cv_homremoval.value == 1) - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // No HOM effect! - else //'development' HOM removal -- makes it blindingly obvious if HOM is spotted. - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 32+(timeinmap&15)); + { + // Clear the software screen buffer to remove HOM + memset(screens[0], 31, vid.width * vid.height * vid.bpp); + } + else + { + //'development' HOM removal -- makes it blindingly obvious if HOM is spotted. + memset(screens[0], 32+(timeinmap&15), vid.width * vid.height * vid.bpp); + } } else if (r_splitscreen == 2 && player == &players[displayplayers[2]]) { diff --git a/src/v_video.cpp b/src/v_video.cpp index 4e1f0d0f7..87a4ddfbe 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1031,18 +1031,20 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) INT32 dupx = vid.dupx, dupy = vid.dupy; if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) - { // Clear the entire screen, from dest to deststop. Yes, this really works. - memset(screens[0], (c&255), vid.width * vid.height * vid.bpp); - return; + { + w = vid.width; + h = vid.height; } + else + { + x *= dupx; + y *= dupy; + w *= dupx; + h *= dupy; - x *= dupx; - y *= dupy; - w *= dupx; - h *= dupy; - - // Center it if necessary - V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); + // Center it if necessary + V_AdjustXYWithSnap(&x, &y, c, dupx, dupy); + } } if (x >= vid.width || y >= vid.height) From 1958394635c5fb9ac69d33529eb3911bb6e42f0c Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 20 Feb 2023 21:27:21 -0600 Subject: [PATCH 12/60] hwr2: add g_wipeskiprender 1-frame workaround --- src/d_main.c | 3 ++ src/f_wipe.c | 10 +++--- src/hwr2/pass_blit_rect.hpp | 4 ++- src/hwr2/pass_resource_managers.cpp | 19 +++--------- src/hwr2/pass_resource_managers.hpp | 11 ++----- src/i_video_common.cpp | 47 ++++++++++++++--------------- src/p_setup.c | 7 ++++- src/y_inter.c | 12 +++++++- 8 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 58df759e4..b2e94818a 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -690,6 +690,9 @@ static void D_Display(void) ps_swaptime = I_GetPreciseTime(); I_FinishUpdate(); // page flip or blit buffer ps_swaptime = I_GetPreciseTime() - ps_swaptime; + + // We should never do the HWR2 skip 3d drawing hack for more than 1 full draw. + g_wipeskiprender = false; } } diff --git a/src/f_wipe.c b/src/f_wipe.c index 435d79fda..5221080a3 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -340,8 +340,8 @@ void F_WipeStartScreen(void) return; } #endif - // wipe_scr_start = screens[3]; - // I_ReadScreen(wipe_scr_start); + wipe_scr_start = screens[3]; + I_ReadScreen(wipe_scr_start); I_FinishUpdateWipeStartScreen(); #endif } @@ -358,9 +358,9 @@ void F_WipeEndScreen(void) return; } #endif - // wipe_scr_end = screens[4]; - // I_ReadScreen(wipe_scr_end); - // V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start); + wipe_scr_end = screens[4]; + I_ReadScreen(wipe_scr_end); + V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start); I_FinishUpdateWipeEndScreen(); #endif } diff --git a/src/hwr2/pass_blit_rect.hpp b/src/hwr2/pass_blit_rect.hpp index d828812cd..010583c3e 100644 --- a/src/hwr2/pass_blit_rect.hpp +++ b/src/hwr2/pass_blit_rect.hpp @@ -30,7 +30,7 @@ class BlitRectPass final : public Pass uint32_t output_width_ = 0; uint32_t output_height_ = 0; bool output_correct_aspect_ = false; - bool output_clear_ = true; + bool output_clear_ = false; bool output_flip_ = false; rhi::Handle render_pass_; rhi::Handle quad_vbo_; @@ -84,6 +84,8 @@ public: output_correct_aspect_ = correct_aspect; output_flip_ = flip; } + + void clear_output(bool clear) noexcept { output_clear_ = clear; } }; } // namespace srb2::hwr2 diff --git a/src/hwr2/pass_resource_managers.cpp b/src/hwr2/pass_resource_managers.cpp index 40efb0980..fb38dcdf4 100644 --- a/src/hwr2/pass_resource_managers.cpp +++ b/src/hwr2/pass_resource_managers.cpp @@ -33,15 +33,10 @@ void FramebufferManager::prepass(Rhi& rhi) // Destroy the framebuffer textures if they exist and the video size changed if (width_ != current_width || height_ != current_height) { - if (main_colors_[0] != kNullHandle) + if (main_color_ != kNullHandle) { - rhi.destroy_texture(main_colors_[0]); - main_colors_[0] = kNullHandle; - } - if (main_colors_[1] != kNullHandle) - { - rhi.destroy_texture(main_colors_[1]); - main_colors_[1] = kNullHandle; + rhi.destroy_texture(main_color_); + main_color_ = kNullHandle; } if (main_depth_ != kNullHandle) { @@ -74,13 +69,9 @@ void FramebufferManager::prepass(Rhi& rhi) height_ = current_height; // Recreate the framebuffer textures - if (main_colors_[0] == kNullHandle) + if (main_color_ == kNullHandle) { - main_colors_[0] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); - } - if (main_colors_[1] == kNullHandle) - { - main_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); + main_color_ = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height}); } if (main_depth_ == kNullHandle) { diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp index 43e9dc56f..30c158572 100644 --- a/src/hwr2/pass_resource_managers.hpp +++ b/src/hwr2/pass_resource_managers.hpp @@ -22,12 +22,11 @@ namespace srb2::hwr2 class FramebufferManager final : public Pass { - std::array, 2> main_colors_; + rhi::Handle main_color_; rhi::Handle main_depth_; std::array, 2> post_colors_; rhi::Handle wipe_start_color_; rhi::Handle wipe_end_color_; - std::size_t main_index_ = 0; std::size_t post_index_ = 0; std::size_t width_ = 0; std::size_t height_ = 0; @@ -42,9 +41,6 @@ public: virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; virtual void postpass(rhi::Rhi& rhi) override; - /// @brief Swap the current and previous main colors. - void swap_main() noexcept { main_index_ = main_index_ == 0 ? 1 : 0; } - /// @brief Swap the current and previous postprocess FB textures. Use between pass prepass phases to alternate. void swap_post() noexcept { @@ -54,9 +50,8 @@ public: void reset_post() noexcept { first_postprocess_ = true; } - rhi::Handle current_main_color() const noexcept { return main_colors_[main_index_]; } + rhi::Handle main_color() const noexcept { return main_color_; } rhi::Handle main_depth() const noexcept { return main_depth_; } - rhi::Handle previous_main_color() const noexcept { return main_colors_[1 - main_index_]; } rhi::Handle current_post_color() const noexcept { return post_colors_[post_index_]; } @@ -64,7 +59,7 @@ public: { if (first_postprocess_) { - return current_main_color(); + return main_color(); } return post_colors_[1 - post_index_]; }; diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index f3371a344..93d73f7a1 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -150,11 +150,11 @@ static InternalPassData build_pass_manager() "3d_prepare", [framebuffer_manager](PassManager& mgr, Rhi&) { - const bool sw_enabled = rendermode == render_soft; + const bool sw_enabled = rendermode == render_soft && gamestate != GS_NULL; mgr.set_pass_enabled("software", sw_enabled); mgr.set_pass_enabled("blit_sw_prepare", sw_enabled); - mgr.set_pass_enabled("blit_sw", sw_enabled); + mgr.set_pass_enabled("blit_sw", sw_enabled && !g_wipeskiprender); } ); basic_rendering->insert("software", software_pass); @@ -163,7 +163,7 @@ static InternalPassData build_pass_manager() [blit_sw_pass, software_pass, framebuffer_manager](PassManager&, Rhi&) { blit_sw_pass->set_texture(software_pass->screen_texture(), vid.width, vid.height); - blit_sw_pass->set_output(framebuffer_manager->current_main_color(), vid.width, vid.height, false, false); + blit_sw_pass->set_output(framebuffer_manager->main_color(), vid.width, vid.height, false, false); } ); basic_rendering->insert("blit_sw", blit_sw_pass); @@ -172,7 +172,7 @@ static InternalPassData build_pass_manager() "2d_prepare", [twodee, framebuffer_manager, palette_manager](PassManager& mgr, Rhi&) { - twodee->output_ = framebuffer_manager->current_main_color(); + twodee->output_ = framebuffer_manager->main_color(); twodee->palette_manager_ = palette_manager; twodee->output_width_ = vid.width; twodee->output_height_ = vid.height; @@ -180,35 +180,17 @@ static InternalPassData build_pass_manager() ); basic_rendering->insert("2d", twodee); - // basic_rendering->insert( - // "pp_final_prepare", - // [](PassManager& mgr, Rhi&) - // { - // mgr.set_pass_enabled("pp_final_wipe_prepare", WipeInAction); - // mgr.set_pass_enabled("pp_final_wipe", WipeInAction); - // mgr.set_pass_enabled("pp_final_wipe_flip", WipeInAction); - // } - // ); basic_rendering->insert( "pp_final_simple_blit_prepare", [pp_simple_blit_pass, framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); - pp_simple_blit_pass->set_texture(framebuffer_manager->current_main_color(), vid.width, vid.height); + pp_simple_blit_pass->set_texture(framebuffer_manager->main_color(), vid.width, vid.height); pp_simple_blit_pass ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true); } ); basic_rendering->insert("pp_final_simple_blit", pp_simple_blit_pass); - // basic_rendering->insert( - // "pp_final_simple_blit_flip", - // [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } - // ); - - // basic_rendering->insert( - // "pp_final_wipe_flip", - // [framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); } - // ); basic_rendering->insert( "screenshot_prepare", @@ -270,18 +252,33 @@ static InternalPassData build_pass_manager() // Wipe End Screen Capture rendering auto wipe_capture_end_rendering = std::make_shared(); auto wipe_end_blit = std::make_shared(); + auto wipe_end_blit_start_to_main = std::make_shared(); wipe_capture_end_rendering->insert("resource_manager", resource_manager); wipe_capture_end_rendering->insert("basic_rendering", basic_rendering); wipe_capture_end_rendering->insert( "wipe_capture_prepare", - [framebuffer_manager, wipe_end_blit](PassManager&, Rhi&) + [framebuffer_manager, wipe_end_blit, wipe_end_blit_start_to_main](PassManager&, Rhi&) { wipe_end_blit->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); wipe_end_blit->set_output(framebuffer_manager->wipe_end_color(), vid.width, vid.height, false, true); + + wipe_end_blit_start_to_main->set_texture( + framebuffer_manager->wipe_start_color(), + vid.width, + vid.height + ); + wipe_end_blit_start_to_main->set_output( + framebuffer_manager->main_color(), + vid.width, + vid.height, + false, + true + ); } ); wipe_capture_end_rendering->insert("wipe_capture", wipe_end_blit); + wipe_capture_end_rendering->insert("wipe_end_blit_start_to_main", wipe_end_blit_start_to_main); // Wipe rendering only runs the wipe shader on the start and end screens, and adds composite-present. auto wipe_rendering = std::make_shared(); @@ -294,7 +291,7 @@ static InternalPassData build_pass_manager() [pp_wipe_pass, framebuffer_manager, common_resources_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); - Handle start = framebuffer_manager->wipe_start_color(); + Handle start = framebuffer_manager->main_color(); Handle end = framebuffer_manager->wipe_end_color(); if (g_wipereverse) { diff --git a/src/p_setup.c b/src/p_setup.c index 0c8ddef6b..20451daba 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -334,7 +334,7 @@ FUNCNORETURN static ATTRNORETURN void CorruptMapError(const char *msg) { sprintf(mapname, "ID %d", gamemap-1); } - + CON_LogMessage("Map "); CON_LogMessage(mapname); CON_LogMessage(" is corrupt: "); @@ -7499,9 +7499,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // This is needed. Don't touch. maptol = mapheaderinfo[gamemap-1]->typeoflevel; + // HWR2 skip 3d render draw hack to avoid losing the current wipe screen + g_wipeskiprender = true; + CON_Drawer(); // let the user know what we are going to do I_FinishUpdate(); // page flip or blit buffer + g_wipeskiprender = false; + // Reset the palette if (rendermode != render_none) V_SetPaletteLump("PLAYPAL"); diff --git a/src/y_inter.c b/src/y_inter.c index 715289525..04b251ec5 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -336,9 +336,14 @@ void Y_IntermissionDrawer(void) { INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = highlightflags; + // If we early return, skip drawing the 3D scene (software buffer) so it doesn't clobber the frame for the wipe + g_wipeskiprender = true; + if (intertype == int_none || rendermode == render_none) return; + g_wipeskiprender = false; + // the merge was kind of a mess, how does this work -- toast 171021 { M_DrawMenuBackground(); @@ -810,7 +815,7 @@ void Y_StartIntermission(void) if (!timer) { // Prevent a weird bug - timer = 1; + timer = 1; } else if (nump < 2 && !netgame) { @@ -945,6 +950,9 @@ void Y_VoteDrawer(void) UINT8 selected[4]; fixed_t rubyheight = 0; + // If we early return, skip drawing the 3D scene (software buffer) so it doesn't clobber the frame for the wipe + g_wipeskiprender = true; + if (rendermode == render_none) return; @@ -954,6 +962,8 @@ void Y_VoteDrawer(void) if (!voteclient.loaded) return; + g_wipeskiprender = false; + { static angle_t rubyfloattime = 0; rubyheight = FINESINE(rubyfloattime>>ANGLETOFINESHIFT); From 7c01136247fb9400b712350627f53e45a42ccebd Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 26 Feb 2023 18:31:19 -0600 Subject: [PATCH 13/60] hwr2: Imgui EndFrame if we didn't Render fixes a failed ImGui assertion in Debug builds --- src/i_video_common.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 93d73f7a1..ef02a7643 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -54,6 +54,7 @@ struct InternalPassData static std::unique_ptr g_passes; static Rhi* g_last_known_rhi = nullptr; +static bool g_imgui_frame_active = false; Handle srb2::sys::g_current_rhi = kNullHandle; @@ -220,6 +221,7 @@ static InternalPassData build_pass_manager() [](PassManager&, Rhi& rhi) {}, [framebuffer_manager](PassManager&, Rhi& rhi) { + g_imgui_frame_active = false; rhi.present(); rhi.finish(); framebuffer_manager->reset_post(); @@ -322,11 +324,16 @@ void I_NewTwodeeFrame(void) void I_NewImguiFrame(void) { - // TODO move this to srb2loop + if (g_imgui_frame_active) + { + ImGui::EndFrame(); + g_imgui_frame_active = false; + } ImGuiIO& io = ImGui::GetIO(); io.DisplaySize.x = vid.realwidth; io.DisplaySize.y = vid.realheight; ImGui::NewFrame(); + g_imgui_frame_active = true; } static void maybe_reinit_passes(Rhi* rhi) From 11fecebc47b7a4940a7b9655e6dbf964ddc5a52c Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 26 Feb 2023 20:18:05 -0600 Subject: [PATCH 14/60] Make AVRecorder WebM an optional part of the build --- CMakeLists.txt | 1 + src/CMakeLists.txt | 18 +++++++++++++----- src/d_netcmd.c | 9 ++++++++- src/hwr2/pass_software.cpp | 5 ++++- src/m_misc.cpp | 12 ++++++++++++ src/sdl/new_sound.cpp | 10 +++++++++- thirdparty/CMakeLists.txt | 15 +++++++++------ 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 455c4b0b4..a83ed5f8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ cmake_dependent_option( OFF "NOT SRB2_CONFIG_SYSTEM_LIBRARIES" OFF ) +option(SRB2_CONFIG_ENABLE_WEBM_MOVIES "Enable WebM recording support" ON) option(SRB2_CONFIG_HWRENDER "Enable hardware render (OpenGL) support" ON) option(SRB2_CONFIG_STATIC_OPENGL "Enable static linking GL (do not do this)" OFF) option(SRB2_CONFIG_ERRORMODE "Compile C code with warnings treated as errors." OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9055926d8..8174dac16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,7 +32,6 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 m_aatree.c m_anigif.c m_argv.c - m_avrecorder.cpp m_bbox.c m_cheat.c m_cond.c @@ -136,6 +135,10 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_roulette.c ) +if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) + target_sources(SRB2SDL2 PRIVATE m_avrecorder.cpp) +endif() + # This updates the modification time for comptime.c at the # end of building so when the build system is ran next time, # that file gets flagged. comptime.c will always be rebuilt. @@ -232,9 +235,12 @@ target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite) target_link_libraries(SRB2SDL2 PRIVATE glad::glad) target_link_libraries(SRB2SDL2 PRIVATE fmt) target_link_libraries(SRB2SDL2 PRIVATE imgui::imgui) -target_link_libraries(SRB2SDL2 PRIVATE webm::libwebm webm::libvpx) -target_link_libraries(SRB2SDL2 PRIVATE libyuv::libyuv) -target_link_libraries(SRB2SDL2 PRIVATE Vorbis::vorbis Vorbis::vorbisenc) +if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) + target_link_libraries(SRB2SDL2 PRIVATE webm::libwebm webm::libvpx) + target_link_libraries(SRB2SDL2 PRIVATE libyuv::libyuv) + target_link_libraries(SRB2SDL2 PRIVATE Vorbis::vorbis Vorbis::vorbisenc) + target_compile_definitions(SRB2SDL2 PRIVATE -DSRB2_CONFIG_ENABLE_WEBM_MOVIES) +endif() target_link_libraries(SRB2SDL2 PRIVATE acsvm) @@ -552,7 +558,9 @@ if(SRB2_CONFIG_ENABLE_TESTS) add_subdirectory(tests) endif() add_subdirectory(menus) -add_subdirectory(media) +if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) + add_subdirectory(media) +endif() # strip debug symbols into separate file when using gcc. # to be consistent with Makefile, don't generate for OS X. diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 53fe94b64..5e93a4a10 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -62,7 +62,10 @@ #include "deh_tables.h" #include "m_perfstats.h" #include "k_specialstage.h" + +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "m_avrecorder.h" +#endif #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -904,7 +907,11 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_moviemode); CV_RegisterVar(&cv_movie_option); CV_RegisterVar(&cv_movie_folder); + +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES M_AVRecorder_AddCommands(); +#endif + // PNG variables CV_RegisterVar(&cv_zlib_level); CV_RegisterVar(&cv_zlib_memory); @@ -4109,7 +4116,7 @@ void Schedule_Insert(scheduleTask_t *addTask) { schedule_size *= 2; } - + schedule = Z_ReallocAlign( (void*) schedule, sizeof(scheduleTask_t*) * schedule_size, diff --git a/src/hwr2/pass_software.cpp b/src/hwr2/pass_software.cpp index aca51a615..a95576f34 100644 --- a/src/hwr2/pass_software.cpp +++ b/src/hwr2/pass_software.cpp @@ -17,7 +17,9 @@ #include "../discord.h" #endif #include "../doomstat.h" +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "../m_avrecorder.h" +#endif #include "../st_stuff.h" #include "../s_sound.h" #include "../st_stuff.h" @@ -59,8 +61,9 @@ static void temp_legacy_finishupdate_draws() } if (cv_mindelay.value && consoleplayer == serverplayer && Playing()) SCR_DisplayLocalPing(); - +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES M_AVRecorder_DrawFrameRate(); +#endif } if (marathonmode) diff --git a/src/m_misc.cpp b/src/m_misc.cpp index f79edb7fb..491be3b30 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -45,7 +45,9 @@ #include "command.h" // cv_execversion #include "m_anigif.h" +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "m_avrecorder.h" +#endif // So that the screenshot menu auto-updates... #include "k_menu.h" @@ -1299,6 +1301,9 @@ static inline moviemode_t M_StartMovieGIF(const char *pathname) static inline moviemode_t M_StartMovieAVRecorder(const char *pathname) { +#ifndef SRB2_CONFIG_ENABLE_WEBM_MOVIES + return MM_OFF; +#else const char *ext = M_AVRecorder_GetFileExtension(); const char *freename; @@ -1314,6 +1319,7 @@ static inline moviemode_t M_StartMovieAVRecorder(const char *pathname) } return MM_AVRECORDER; +#endif } void M_StartMovie(void) @@ -1366,11 +1372,13 @@ void M_StartMovie(void) CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "GIF"); else if (moviemode == MM_SCREENSHOT) CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "screenshots"); +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES else if (moviemode == MM_AVRECORDER) { CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), M_AVRecorder_GetCurrentFormat()); M_AVRecorder_PrintCurrentConfiguration(); } +#endif //singletics = (moviemode != MM_OFF); #endif @@ -1382,6 +1390,7 @@ void M_SaveFrame(void) // paranoia: should be unnecessary without singletics static tic_t oldtic = 0; +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES if (moviemode == MM_AVRECORDER) { // TODO: replace once hwr2 twodee is finished @@ -1396,6 +1405,7 @@ void M_SaveFrame(void) } return; } +#endif // skip interpolated frames for other modes if (oldtic == I_GetTime()) @@ -1485,9 +1495,11 @@ void M_StopMovie(void) #endif case MM_SCREENSHOT: break; +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES case MM_AVRECORDER: M_AVRecorder_Close(); break; +#endif default: return; } diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp index 228985c83..80f549508 100644 --- a/src/sdl/new_sound.cpp +++ b/src/sdl/new_sound.cpp @@ -21,7 +21,10 @@ #include "../audio/sound_effect_player.hpp" #include "../cxxutil.hpp" #include "../io/streams.hpp" + +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "../m_avrecorder.hpp" +#endif #include "../doomdef.h" #include "../i_sound.h" @@ -58,7 +61,9 @@ static shared_ptr> gain_music; static vector> sound_effect_channels; +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES static shared_ptr av_recorder; +#endif static void (*music_fade_callback)(); @@ -138,9 +143,10 @@ void audio_callback(void* userdata, Uint8* buffer, int len) std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f), }; } - +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES if (av_recorder) av_recorder->push_audio_samples(tcb::span {float_buffer, float_len}); +#endif } catch (...) { @@ -758,8 +764,10 @@ boolean I_FadeInPlaySong(UINT32 ms, boolean looping) void I_UpdateAudioRecorder(void) { +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES // must be locked since av_recorder is used by audio_callback SdlAudioLockHandle _; av_recorder = g_av_recorder; +#endif } diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 6fa5e9687..0c77d8c1b 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -10,14 +10,11 @@ else() endif() if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") -include("cpm-sdl2.cmake") + include("cpm-sdl2.cmake") include("cpm-zlib.cmake") include("cpm-png.cmake") include("cpm-curl.cmake") include("cpm-libgme.cmake") - include("cpm-libvpx.cmake") - include("cpm-ogg.cmake") # libvorbis depends - include("cpm-libvorbis.cmake") endif() include("cpm-rapidjson.cmake") @@ -26,8 +23,14 @@ include("cpm-xmp-lite.cmake") include("cpm-fmt.cmake") include("cpm-imgui.cmake") include("cpm-acsvm.cmake") -include("cpm-libwebm.cmake") -include("cpm-libyuv.cmake") + +if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND SRB2_CONFIG_ENABLE_WEBM_MOVIES) + include("cpm-libvpx.cmake") + include("cpm-ogg.cmake") # libvorbis depends + include("cpm-libvorbis.cmake") + include("cpm-libwebm.cmake") + include("cpm-libyuv.cmake") +endif() add_subdirectory(tcbrindle_span) add_subdirectory(stb_vorbis) From a19b476d3de82caac6abe5e5409adf201dd4c1fa Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 13 Feb 2023 21:41:25 -0800 Subject: [PATCH 15/60] hwr2: do GIF recording in screenshot pass --- src/d_clisrv.c | 2 +- src/d_main.c | 2 +- src/f_finale.c | 2 +- src/f_wipe.c | 2 +- src/g_game.c | 2 +- src/hwr2/pass_screenshot.cpp | 13 +++++++++++-- src/m_anigif.c | 30 +++++++++++++++++++++++++----- src/m_anigif.h | 1 + src/m_misc.cpp | 31 ++++++++++++++++++++++++++++++- src/m_misc.h | 3 ++- src/p_setup.c | 2 +- 11 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f53d1f35a..f251d0c9b 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2022,7 +2022,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic } I_UpdateNoVsync(); // page flip or blit buffer if (moviemode) - M_SaveFrame(); + M_LegacySaveFrame(); S_UpdateSounds(); S_UpdateClosedCaptions(); } diff --git a/src/d_main.c b/src/d_main.c index b2e94818a..5b21248c0 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -873,7 +873,7 @@ void D_SRB2Loop(void) // Only take screenshots after drawing. if (moviemode) - M_SaveFrame(); + M_LegacySaveFrame(); #ifdef HWRENDER if (rendermode == render_opengl && takescreenshot) M_DoLegacyGLScreenShot(); diff --git a/src/f_finale.c b/src/f_finale.c index af0ab00dc..b78bc4139 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -438,7 +438,7 @@ void F_IntroTicker(void) 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(); + M_LegacySaveFrame(); } } diff --git a/src/f_wipe.c b/src/f_wipe.c index 5221080a3..1beb29506 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -526,7 +526,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r } if (moviemode) - M_SaveFrame(); + M_LegacySaveFrame(); NetKeepAlive(); // Update the network so we don't cause timeouts } diff --git a/src/g_game.c b/src/g_game.c index 706c9fdb5..ab9783b93 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1602,7 +1602,7 @@ void G_PreLevelTitleCard(void) NetKeepAlive(); // Prevent timeouts if (moviemode) - M_SaveFrame(); + M_LegacySaveFrame(); while (!((nowtime = I_GetTime()) - lasttime)) { diff --git a/src/hwr2/pass_screenshot.cpp b/src/hwr2/pass_screenshot.cpp index bd9a69bf6..b37409218 100644 --- a/src/hwr2/pass_screenshot.cpp +++ b/src/hwr2/pass_screenshot.cpp @@ -34,7 +34,7 @@ void ScreenshotPass::prepass(Rhi& rhi) ); } - doing_screenshot_ = takescreenshot; + doing_screenshot_ = takescreenshot || moviemode == MM_GIF; } void ScreenshotPass::transfer(Rhi& rhi, Handle ctx) @@ -63,6 +63,15 @@ void ScreenshotPass::postpass(Rhi& rhi) return; } - M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); + if (takescreenshot) + { + M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); + } + + if (moviemode == MM_GIF) + { + M_SaveFrame(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); + } + doing_screenshot_ = false; } diff --git a/src/m_anigif.c b/src/m_anigif.c index a6c830fa3..0bd14f422 100644 --- a/src/m_anigif.c +++ b/src/m_anigif.c @@ -508,7 +508,7 @@ static size_t gifframe_size = 8192; #ifdef HWRENDER static colorlookup_t gif_colorlookup; -static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr) +static void GIF_rgbconvert(const UINT8 *linear, UINT8 *scr) { UINT8 r, g, b; size_t src = 0, dest = 0; @@ -532,13 +532,18 @@ static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr) // GIF_framewrite // writes a frame into the file. // -static void GIF_framewrite(void) +static void GIF_framewrite(INT32 input_width, INT32 input_height, const UINT8 *input) { UINT8 *p; UINT8 *movie_screen = screens[2]; INT32 blitx, blity, blitw, blith; boolean palchanged; + (void)input_width; + (void)input_height; + + I_Assert(input_width == vid.width && input_height == vid.height); + if (!gifframe_data) gifframe_data = Z_Malloc(gifframe_size, PU_STATIC, NULL); p = gifframe_data; @@ -565,7 +570,10 @@ static void GIF_framewrite(void) // blit to temp screen if (rendermode == render_soft) - I_ReadScreen(movie_screen); + { + I_Assert(input != NULL); + GIF_rgbconvert(input, movie_screen); + } #ifdef HWRENDER else if (rendermode == render_opengl) { @@ -594,7 +602,10 @@ static void GIF_framewrite(void) // Copy the first frame into the movie screen // OpenGL already does the same above. if (gif_frames == 0 && rendermode == render_soft) - I_ReadScreen(movie_screen); + { + I_Assert(input != NULL); + GIF_rgbconvert(input, screens[0]); + } movie_screen = screens[0]; } @@ -751,7 +762,16 @@ INT32 GIF_open(const char *filename) void GIF_frame(void) { // there's not much actually needed here, is there. - GIF_framewrite(); + GIF_framewrite(vid.width, vid.height, NULL); +} + +// +// GIF_frame_rgb24 +// writes a frame into the output gif, with existing image data +// +void GIF_frame_rgb24(INT32 width, INT32 height, const UINT8 *buffer) +{ + GIF_framewrite(width, height, buffer); } // diff --git a/src/m_anigif.h b/src/m_anigif.h index 9ee209aea..34f149a88 100644 --- a/src/m_anigif.h +++ b/src/m_anigif.h @@ -28,6 +28,7 @@ extern "C" { #ifdef HAVE_ANIGIF INT32 GIF_open(const char *filename); void GIF_frame(void); +void GIF_frame_rgb24(INT32 width, INT32 height, const UINT8 *buffer); INT32 GIF_close(void); #endif diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 491be3b30..9862fcf3f 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -1384,9 +1384,18 @@ void M_StartMovie(void) #endif } -void M_SaveFrame(void) +void M_LegacySaveFrame(void) { #if NUMSCREENS > 2 + // TODO: until HWR2 replaces legacy OpenGL renderer, this + // function still needs to called for OpenGL. +#ifdef HWRENDER + if (rendermode != render_opengl) +#endif + { + return; + } + // paranoia: should be unnecessary without singletics static tic_t oldtic = 0; @@ -1463,6 +1472,26 @@ void M_SaveFrame(void) #endif } +void M_SaveFrame(uint32_t width, uint32_t height, tcb::span data) +{ + if (moviemode != MM_GIF) + { + return; + } + + static tic_t oldtic = 0; + + // limit the recording to TICRATE + if (oldtic == I_GetTime()) + { + return; + } + + oldtic = I_GetTime(); + + GIF_frame_rgb24(width, height, reinterpret_cast(data.data())); +} + void M_StopMovie(void) { #if NUMSCREENS > 2 diff --git a/src/m_misc.h b/src/m_misc.h index 2610d2f01..8012c442d 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -28,6 +28,7 @@ #include void M_DoScreenShot(uint32_t width, uint32_t height, tcb::span data); +void M_SaveFrame(uint32_t width, uint32_t height, tcb::span data); extern "C" { #endif @@ -48,7 +49,7 @@ extern consvar_t cv_zlib_memorya, cv_zlib_levela, cv_zlib_strategya, cv_zlib_win extern consvar_t cv_apng_delay, cv_apng_downscale; void M_StartMovie(void); -void M_SaveFrame(void); +void M_LegacySaveFrame(void); void M_StopMovie(void); // the file where game vars and settings are saved diff --git a/src/p_setup.c b/src/p_setup.c index 20451daba..a5140cbb2 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7571,7 +7571,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } \ lastwipetic = nowtime; \ if (moviemode) \ - M_SaveFrame(); \ + M_LegacySaveFrame(); \ NetKeepAlive(); \ } \ From 57cf6dab26e356772e36537fd2af86bc8b874cd1 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 26 Feb 2023 21:20:16 -0800 Subject: [PATCH 16/60] Director: fix positiondelay debounce I'll be honest, this is hard to wrap my head around but it seems to be saying if the currently viewed player is not who we should be viewing, then switch to the new player even if the new player's position is fluctuating. --- src/k_director.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_director.c b/src/k_director.c index 4f5737b4b..9a0e79608 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -291,7 +291,7 @@ void K_UpdateDirector(void) } // even if we're not certain, if we're certain we're watching the WRONG player, try to switch - if (players[*displayplayerp].position != targetposition+1 && !players[target].positiondelay) + if (players[*displayplayerp].position != targetposition+1 && !players[*displayplayerp].positiondelay) { K_DirectorSwitch(target, false); } From 9d8c2cd70316f055800f146f0942ce495ba5b207 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 26 Feb 2023 21:39:37 -0800 Subject: [PATCH 17/60] Director: skip splitscreen players when switching This makes sure director only tries switching to players who aren't splitscreen players. --- src/k_director.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/k_director.c b/src/k_director.c index 51158f8eb..8e62fe521 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -285,8 +285,20 @@ void K_UpdateDirector(void) target = directorinfo.sortedplayers[targetposition]; + // stop here since we're already viewing this player + if (*displayplayerp == target) + { + break; + } + + // if this is a splitscreen player, try next pair + if (P_IsDisplayPlayer(&players[target])) + { + continue; + } + // if we're certain the back half of the pair is actually in this position, try to switch - if (*displayplayerp != target && !players[target].positiondelay) + if (!players[target].positiondelay) { K_DirectorSwitch(target, false); } From 98ce7146149d170a13ed522226594a4926f706e9 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 27 Feb 2023 19:49:45 -0600 Subject: [PATCH 18/60] hwr2: integrate avrecorder with RHI and legacy GL --- src/d_clisrv.c | 8 +++-- src/d_main.c | 6 ++-- src/f_finale.c | 6 ++-- src/f_wipe.c | 4 ++- src/g_game.c | 4 ++- src/hwr2/pass_screenshot.cpp | 4 +-- src/i_video_common.cpp | 12 +++++-- src/m_avrecorder.cpp | 21 ----------- src/m_avrecorder.h | 3 -- src/m_misc.cpp | 60 +++++++++++++++++++++++++++----- src/media/avrecorder.hpp | 13 ++++--- src/media/avrecorder_impl.hpp | 5 ++- src/media/avrecorder_indexed.cpp | 22 ++++++------ src/media/avrecorder_queue.cpp | 2 +- src/p_setup.c | 4 +-- 15 files changed, 105 insertions(+), 69 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f251d0c9b..69d400866 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1879,7 +1879,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic return false; } case CL_LOADFILES: - if (CL_LoadServerFiles()) + if (CL_LoadServerFiles()) cl_mode = CL_SETUPFILES; break; @@ -2021,8 +2021,10 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic #endif } I_UpdateNoVsync(); // page flip or blit buffer - if (moviemode) +#ifdef HWRENDER + if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); +#endif S_UpdateSounds(); S_UpdateClosedCaptions(); } @@ -2540,7 +2542,7 @@ void CL_ClearPlayer(INT32 playernum) if (gamestate == GS_LEVEL) { if (players[playernum].follower) - { + { K_RemoveFollower(&players[playernum]); } diff --git a/src/d_main.c b/src/d_main.c index 5b21248c0..007071e88 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -871,10 +871,10 @@ void D_SRB2Loop(void) D_Display(); } - // Only take screenshots after drawing. - if (moviemode) - M_LegacySaveFrame(); #ifdef HWRENDER + // Only take screenshots after drawing. + if (moviemode && rendermode == render_opengl) + M_LegacySaveFrame(); if (rendermode == render_opengl && takescreenshot) M_DoLegacyGLScreenShot(); #endif diff --git a/src/f_finale.c b/src/f_finale.c index b78bc4139..49b5f31ce 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -437,8 +437,10 @@ void F_IntroTicker(void) #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 +#ifdef HWRENDER + if (moviemode && rendermode == render_opengl) // make sure we save frames for the white hold too M_LegacySaveFrame(); +#endif } } @@ -464,7 +466,7 @@ void F_IntroTicker(void) // check for skipping if (keypressed) keypressed = false; - + if (animtimer > 0) animtimer--; } diff --git a/src/f_wipe.c b/src/f_wipe.c index 1beb29506..2f0350cb7 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -525,8 +525,10 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r g_wipeskiprender = true; } - if (moviemode) +#ifdef HWRENDER + if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); +#endif NetKeepAlive(); // Update the network so we don't cause timeouts } diff --git a/src/g_game.c b/src/g_game.c index ab9783b93..bf2c2761a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1601,8 +1601,10 @@ void G_PreLevelTitleCard(void) I_FinishUpdate(); // page flip or blit buffer NetKeepAlive(); // Prevent timeouts - if (moviemode) +#ifdef HWRENDER + if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); +#endif while (!((nowtime = I_GetTime()) - lasttime)) { diff --git a/src/hwr2/pass_screenshot.cpp b/src/hwr2/pass_screenshot.cpp index b37409218..64ed1e4be 100644 --- a/src/hwr2/pass_screenshot.cpp +++ b/src/hwr2/pass_screenshot.cpp @@ -34,7 +34,7 @@ void ScreenshotPass::prepass(Rhi& rhi) ); } - doing_screenshot_ = takescreenshot || moviemode == MM_GIF; + doing_screenshot_ = takescreenshot || moviemode != MM_OFF; } void ScreenshotPass::transfer(Rhi& rhi, Handle ctx) @@ -68,7 +68,7 @@ void ScreenshotPass::postpass(Rhi& rhi) M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); } - if (moviemode == MM_GIF) + if (moviemode != MM_OFF) { M_SaveFrame(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); } diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index ef02a7643..ef76d1e58 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -188,7 +188,7 @@ static InternalPassData build_pass_manager() framebuffer_manager->swap_post(); pp_simple_blit_pass->set_texture(framebuffer_manager->main_color(), vid.width, vid.height); pp_simple_blit_pass - ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true); + ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, false); } ); basic_rendering->insert("pp_final_simple_blit", pp_simple_blit_pass); @@ -211,7 +211,7 @@ static InternalPassData build_pass_manager() [final_composite_pass, framebuffer_manager](PassManager&, Rhi&) { final_composite_pass->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); - final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, true); + final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, false); } ); composite_present_rendering->insert("final_composite", final_composite_pass); @@ -305,6 +305,14 @@ static InternalPassData build_pass_manager() } ); wipe_rendering->insert("pp_final_wipe", pp_wipe_pass); + wipe_rendering->insert( + "screenshot_prepare", + [screenshot_pass, framebuffer_manager](PassManager&, Rhi&) + { + screenshot_pass->set_source(framebuffer_manager->current_post_color(), vid.width, vid.height); + } + ); + wipe_rendering->insert("screenshot", screenshot_pass); wipe_rendering->insert("composite_present_rendering", composite_present_rendering); InternalPassData ret; diff --git a/src/m_avrecorder.cpp b/src/m_avrecorder.cpp index 7491fb195..7467e388d 100644 --- a/src/m_avrecorder.cpp +++ b/src/m_avrecorder.cpp @@ -229,24 +229,3 @@ void M_AVRecorder_DrawFrameRate(void) g_av_recorder->draw_statistics(); } - -// TODO: remove once hwr2 twodee is finished -void M_AVRecorder_CopySoftwareScreen(void) -{ - SRB2_ASSERT(g_av_recorder != nullptr); - - auto frame = g_av_recorder->new_indexed_video_frame(vid.width, vid.height); - - if (!frame) - { - return; - } - - tcb::span pal(&pLocalPalette[std::max(st_palette, 0) * 256], 256); - tcb::span scr(screens[0], vid.width * vid.height); - - std::copy(pal.begin(), pal.end(), frame->palette.begin()); - std::copy(scr.begin(), scr.end(), frame->screen.begin()); - - g_av_recorder->push_indexed_video_frame(std::move(frame)); -} diff --git a/src/m_avrecorder.h b/src/m_avrecorder.h index 9cce8349c..21d635c5f 100644 --- a/src/m_avrecorder.h +++ b/src/m_avrecorder.h @@ -34,9 +34,6 @@ void M_AVRecorder_PrintCurrentConfiguration(void); void M_AVRecorder_DrawFrameRate(void); -// TODO: remove once hwr2 twodee is finished -void M_AVRecorder_CopySoftwareScreen(void); - extern consvar_t cv_movie_custom_resolution, cv_movie_duration, diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 9862fcf3f..0873a9777 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -47,6 +47,7 @@ #include "m_anigif.h" #ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "m_avrecorder.h" +#include "m_avrecorder.hpp" #endif // So that the screenshot menu auto-updates... @@ -1384,6 +1385,8 @@ void M_StartMovie(void) #endif } +static void M_SaveFrame_AVRecorder(uint32_t width, uint32_t height, tcb::span data); + void M_LegacySaveFrame(void) { #if NUMSCREENS > 2 @@ -1402,17 +1405,11 @@ void M_LegacySaveFrame(void) #ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES if (moviemode == MM_AVRECORDER) { - // TODO: replace once hwr2 twodee is finished - if (rendermode == render_soft) - { - M_AVRecorder_CopySoftwareScreen(); - } - if (M_AVRecorder_IsExpired()) { M_StopMovie(); + return; } - return; } #endif @@ -1464,6 +1461,15 @@ void M_LegacySaveFrame(void) } #else moviemode = MM_OFF; +#endif + return; + case MM_AVRECORDER: +#if defined(SRB2_CONFIG_ENABLE_WEBM_MOVIES) && defined(HWRENDER) + { + UINT8 *linear = HWR_GetScreenshot(); + M_SaveFrame_AVRecorder(vid.width, vid.height, tcb::as_bytes(tcb::span(linear, 3 * vid.width * vid.height))); + free(linear); + } #endif return; default: @@ -1472,7 +1478,7 @@ void M_LegacySaveFrame(void) #endif } -void M_SaveFrame(uint32_t width, uint32_t height, tcb::span data) +static void M_SaveFrame_GIF(uint32_t width, uint32_t height, tcb::span data) { if (moviemode != MM_GIF) { @@ -1492,6 +1498,44 @@ void M_SaveFrame(uint32_t width, uint32_t height, tcb::span dat GIF_frame_rgb24(width, height, reinterpret_cast(data.data())); } +static void M_SaveFrame_AVRecorder(uint32_t width, uint32_t height, tcb::span data) +{ +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES + if (M_AVRecorder_IsExpired()) + { + M_StopMovie(); + return; + } + + auto frame = g_av_recorder->new_staging_video_frame(width, height); + if (!frame) + { + // Not time to submit a frame! + return; + } + + auto data_begin = reinterpret_cast(data.data()); + auto data_end = reinterpret_cast(data.data() + data.size_bytes()); + std::copy(data_begin, data_end, frame->screen.begin()); + g_av_recorder->push_staging_video_frame(std::move(frame)); +#endif +} + +void M_SaveFrame(uint32_t width, uint32_t height, tcb::span data) +{ + switch (moviemode) + { + case MM_GIF: + M_SaveFrame_GIF(width, height, data); + break; + case MM_AVRECORDER: + M_SaveFrame_AVRecorder(width, height, data); + break; + default: + break; + } +} + void M_StopMovie(void) { #if NUMSCREENS > 2 diff --git a/src/media/avrecorder.hpp b/src/media/avrecorder.hpp index fdee91b18..3f2cf684a 100644 --- a/src/media/avrecorder.hpp +++ b/src/media/avrecorder.hpp @@ -58,17 +58,16 @@ public: }; // TODO: remove once hwr2 twodee is finished - struct IndexedVideoFrame + struct StagingVideoFrame { - using instance_t = std::unique_ptr; + using instance_t = std::unique_ptr; - std::array palette; std::vector screen; uint32_t width, height; int pts; - IndexedVideoFrame(uint32_t width_, uint32_t height_, int pts_) : - screen(width_ * height_), width(width_), height(height_), pts(pts_) + StagingVideoFrame(uint32_t width_, uint32_t height_, int pts_) : + screen(width_ * height_ * 3), width(width_), height(height_), pts(pts_) { } }; @@ -87,9 +86,9 @@ public: // May return nullptr in case called between units of // Config::frame_rate - IndexedVideoFrame::instance_t new_indexed_video_frame(uint32_t width, uint32_t height); + StagingVideoFrame::instance_t new_staging_video_frame(uint32_t width, uint32_t height); - void push_indexed_video_frame(IndexedVideoFrame::instance_t frame); + void push_staging_video_frame(StagingVideoFrame::instance_t frame); // Proper name of the container format. const char* format_name() const; diff --git a/src/media/avrecorder_impl.hpp b/src/media/avrecorder_impl.hpp index 3fab89df8..bc4338932 100644 --- a/src/media/avrecorder_impl.hpp +++ b/src/media/avrecorder_impl.hpp @@ -53,7 +53,7 @@ public: template struct Traits { - using frame_type = IndexedVideoFrame::instance_t; + using frame_type = StagingVideoFrame::instance_t; }; std::vector::frame_type> vec_; @@ -151,8 +151,7 @@ private: void container_dtor_handler(const MediaContainer& container) const; - // TODO: remove once hwr2 twodee is finished - VideoFrame::instance_t convert_indexed_video_frame(const IndexedVideoFrame& indexed); + VideoFrame::instance_t convert_staging_video_frame(const StagingVideoFrame& indexed); }; template <> diff --git a/src/media/avrecorder_indexed.cpp b/src/media/avrecorder_indexed.cpp index 31846b7da..f69008b85 100644 --- a/src/media/avrecorder_indexed.cpp +++ b/src/media/avrecorder_indexed.cpp @@ -21,34 +21,36 @@ using namespace srb2::media; using Impl = AVRecorder::Impl; -VideoFrame::instance_t Impl::convert_indexed_video_frame(const IndexedVideoFrame& indexed) +VideoFrame::instance_t Impl::convert_staging_video_frame(const StagingVideoFrame& staging) { - VideoFrame::instance_t frame = video_encoder_->new_frame(indexed.width, indexed.height, indexed.pts); + VideoFrame::instance_t frame = video_encoder_->new_frame(staging.width, staging.height, staging.pts); SRB2_ASSERT(frame != nullptr); const VideoFrame::Buffer& buffer = frame->rgba_buffer(); - const uint8_t* s = indexed.screen.data(); + const uint8_t* s = staging.screen.data(); uint8_t* p = buffer.plane.data(); + // Convert from RGB8 to RGBA8 for (int y = 0; y < frame->height(); ++y) { for (int x = 0; x < frame->width(); ++x) { - const RGBA_t& c = indexed.palette[s[x]]; - - reinterpret_cast(p)[x] = c.rgba; + p[x * 4] = s[x * 3]; + p[x * 4 + 1] = s[x * 3 + 1]; + p[x * 4 + 2] = s[x * 3 + 2]; + p[x * 4 + 3] = 255; } - s += indexed.width; + s += staging.width * 3; p += buffer.row_stride; } return frame; } -AVRecorder::IndexedVideoFrame::instance_t AVRecorder::new_indexed_video_frame(uint32_t width, uint32_t height) +AVRecorder::StagingVideoFrame::instance_t AVRecorder::new_staging_video_frame(uint32_t width, uint32_t height) { std::optional pts = impl_->advance_video_pts(); @@ -57,10 +59,10 @@ AVRecorder::IndexedVideoFrame::instance_t AVRecorder::new_indexed_video_frame(ui return nullptr; } - return std::make_unique(width, height, *pts); + return std::make_unique(width, height, *pts); } -void AVRecorder::push_indexed_video_frame(IndexedVideoFrame::instance_t frame) +void AVRecorder::push_staging_video_frame(StagingVideoFrame::instance_t frame) { auto _ = impl_->queue_guard(); diff --git a/src/media/avrecorder_queue.cpp b/src/media/avrecorder_queue.cpp index 4a94e1abe..f18c7b8fd 100644 --- a/src/media/avrecorder_queue.cpp +++ b/src/media/avrecorder_queue.cpp @@ -125,7 +125,7 @@ Impl::QueueState Impl::encode_queues() { for (auto& p : copy) { - auto frame = convert_indexed_video_frame(*p); + auto frame = convert_staging_video_frame(*p); video_encoder_->encode(std::move(frame)); } diff --git a/src/p_setup.c b/src/p_setup.c index a5140cbb2..6ae609536 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -380,7 +380,7 @@ void P_DeleteFlickies(INT16 i) static void P_ClearSingleMapHeaderInfo(INT16 num) { UINT8 i = 0; - + mapheaderinfo[num]->lvlttl[0] = '\0'; mapheaderinfo[num]->subttl[0] = '\0'; mapheaderinfo[num]->zonttl[0] = '\0'; @@ -7570,7 +7570,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) I_UpdateTime(cv_timescale.value); \ } \ lastwipetic = nowtime; \ - if (moviemode) \ + if (moviemode && rendermode == render_opengl) \ M_LegacySaveFrame(); \ NetKeepAlive(); \ } \ From 83b6c309520c384dfa7970a15995cf45c0d51d11 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 27 Feb 2023 21:45:59 -0600 Subject: [PATCH 19/60] hwr2: add wipe mode to F_RunWipe, fix all wipes --- src/d_main.c | 6 +++--- src/f_finale.c | 8 ++++---- src/f_finale.h | 11 ++++++----- src/f_wipe.c | 21 ++++++++++++--------- src/hwr2/pass_postprocess.cpp | 9 +++++---- src/k_menudraw.c | 2 +- src/k_menufunc.c | 4 ++-- src/menus/play-local-race-time-attack.c | 2 +- src/menus/transient/cup-select.c | 2 +- src/menus/transient/level-select.c | 2 +- src/p_setup.c | 14 ++++++++------ 11 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 007071e88..a543ea333 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -343,7 +343,7 @@ static void D_Display(void) F_WipeStartScreen(); F_WipeColorFill(31); F_WipeEndScreen(); - F_RunWipe(wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false); + F_RunWipe(wipedefindex, wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false); } if (gamestate != GS_LEVEL && rendermode != render_none) @@ -356,7 +356,7 @@ static void D_Display(void) } else //dedicated servers { - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false); + F_RunWipe(wipedefindex, wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false); wipegamestate = gamestate; } @@ -633,7 +633,7 @@ static void D_Display(void) { F_WipeEndScreen(); - F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false); + F_RunWipe(wipedefindex, wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false); } // reset counters so timedemo doesn't count the wipe duration diff --git a/src/f_finale.c b/src/f_finale.c index 49b5f31ce..acd923765 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -313,7 +313,7 @@ void F_StartIntro(void) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_intro_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_intro_toblack, wipedefs[wipe_intro_toblack], false, "FADEMAP0", false, false); } S_StopMusic(); @@ -409,7 +409,7 @@ void F_IntroTicker(void) F_WipeStartScreen(); F_WipeColorFill(31); F_WipeEndScreen(); - F_RunWipe(99, true, "FADEMAP0", false, false); + F_RunWipe(wipe_intro_toblack, 99, true, "FADEMAP0", false, false); } // Stay on black for a bit. =) @@ -2437,7 +2437,7 @@ void F_CutsceneDrawer(void) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, cutscenes[cutnum]->scene[scenenum].fadecolor); F_WipeEndScreen(); - F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeinid, true, NULL, false, false); + F_RunWipe(wipe_intro_toblack, cutscenes[cutnum]->scene[scenenum].fadeinid, true, NULL, false, false); F_WipeStartScreen(); } @@ -2457,7 +2457,7 @@ void F_CutsceneDrawer(void) if (dofadenow && rendermode != render_none) { F_WipeEndScreen(); - F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true, NULL, false, false); + F_RunWipe(wipe_intro_toblack, cutscenes[cutnum]->scene[scenenum].fadeoutid, true, NULL, false, false); } V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, cutscene_disptext); diff --git a/src/f_finale.h b/src/f_finale.h index 970bf0619..c5972f9d1 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -140,6 +140,7 @@ extern UINT16 curtttics; // extern boolean WipeInAction; +extern UINT8 g_wipemode; extern UINT8 g_wipetype; extern UINT8 g_wipeframe; extern boolean g_wipereverse; @@ -155,19 +156,19 @@ extern INT32 lastwipetic; void F_WipeStartScreen(void); void F_WipeEndScreen(void); -void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle); +void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle); void F_WipeStageTitle(void); #define F_WipeColorFill(c) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, c) tic_t F_GetWipeLength(UINT8 wipetype); boolean F_WipeExists(UINT8 wipetype); /// @brief true if the wipetype is to-black -boolean F_WipeIsToBlack(UINT8 wipetype); +boolean F_WipeIsToBlack(UINT8 wipemode); /// @brief true if the wipetype is to-white -boolean F_WipeIsToWhite(UINT8 wipetype); +boolean F_WipeIsToWhite(UINT8 wipemode); /// @brief true if the wipetype is to-invert -boolean F_WipeIsToInvert(UINT8 wipetype); +boolean F_WipeIsToInvert(UINT8 wipemode); /// @brief true if the wipetype is modulated from the previous frame -boolean F_WipeIsCrossfade(UINT8 wipetype); +boolean F_WipeIsCrossfade(UINT8 wipemode); enum { diff --git a/src/f_wipe.c b/src/f_wipe.c index 2f0350cb7..2068ac6d5 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -222,6 +222,7 @@ static boolean g_wipedef_crossfade[NUMWIPEDEFS] = { //-------------------------------------------------------------------------- boolean WipeInAction = false; +UINT8 g_wipemode = 0; UINT8 g_wipetype = 0; UINT8 g_wipeframe = 0; boolean g_wipereverse = false; @@ -425,9 +426,10 @@ void F_WipeStageTitle(void) /** After setting up the screens you want to wipe, * calling this will do a 'typical' wipe. */ -void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle) +void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *colormap, boolean reverse, boolean encorewiggle) { #ifdef NOWIPE + (void)wipemode; (void)wipetype; (void)drawMenu; (void)colormap; @@ -489,6 +491,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers { // F_DoWipe(fmask, fcolor, reverse); + g_wipemode = wipemode; g_wipetype = wipetype; g_wipeframe = wipeframe - 1; g_wipereverse = reverse; @@ -593,23 +596,23 @@ boolean F_WipeExists(UINT8 wipetype) #endif } -boolean F_WipeIsToBlack(UINT8 wipetype) +boolean F_WipeIsToBlack(UINT8 wipemode) { - return g_wipedef_toblack[wipetype]; + return g_wipedef_toblack[wipemode]; } -boolean F_WipeIsToWhite(UINT8 wipetype) +boolean F_WipeIsToWhite(UINT8 wipemode) { - return g_wipedef_towhite[wipetype]; + return g_wipedef_towhite[wipemode]; } -boolean F_WipeIsToInvert(UINT8 wipetype) +boolean F_WipeIsToInvert(UINT8 wipemode) { - return g_wipedef_toinvert[wipetype]; + return g_wipedef_toinvert[wipemode]; } -boolean F_WipeIsCrossfade(UINT8 wipetype) +boolean F_WipeIsCrossfade(UINT8 wipemode) { - return g_wipedef_crossfade[wipetype]; + return g_wipedef_crossfade[wipemode]; } diff --git a/src/hwr2/pass_postprocess.cpp b/src/hwr2/pass_postprocess.cpp index 333338159..240ef1c47 100644 --- a/src/hwr2/pass_postprocess.cpp +++ b/src/hwr2/pass_postprocess.cpp @@ -86,24 +86,25 @@ void PostprocessWipePass::prepass(Rhi& rhi) upload_ibo_ = true; } + uint32_t wipe_mode = g_wipemode; uint32_t wipe_type = g_wipetype; uint32_t wipe_frame = g_wipeframe; bool wipe_reverse = g_wipereverse; wipe_color_mode_ = 0; // TODO 0 = modulate, 1 = invert, 2 = MD to black, 3 = MD to white - if (F_WipeIsToBlack(wipe_type)) + if (F_WipeIsToBlack(wipe_mode)) { wipe_color_mode_ = 2; } - else if (F_WipeIsToWhite(wipe_type)) + else if (F_WipeIsToWhite(wipe_mode)) { wipe_color_mode_ = 3; } - else if (F_WipeIsToInvert(wipe_type)) + else if (F_WipeIsToInvert(wipe_mode)) { wipe_color_mode_ = 1; } - else if (F_WipeIsCrossfade(wipe_type)) + else if (F_WipeIsCrossfade(wipe_mode)) { wipe_color_mode_ = 0; } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index a78423721..6207f0e24 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -553,7 +553,7 @@ void M_Drawer(void) if (menuwipe) { F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_menu_final], false, "FADEMAP0", true, false); + F_RunWipe(wipe_menu_final, wipedefs[wipe_menu_final], false, "FADEMAP0", true, false); menuwipe = false; } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 05d282e7f..8898bee71 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -601,7 +601,7 @@ void M_SetupNextMenu(menu_t *menudef, boolean notransition) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_menu_toblack, wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); } } @@ -1034,7 +1034,7 @@ void M_Ticker(void) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_menu_toblack, wipedefs[wipe_menu_toblack], false, "FADEMAP0", false, false); } M_SetupNextMenu(menutransition.endmenu, true); diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 054681792..d41f29dca 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -446,7 +446,7 @@ void M_StartTimeAttack(INT32 choice) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); SV_StartSinglePlayerServer(levellist.newgametype, false); diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index 6484d45f5..23a85e4e0 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -112,7 +112,7 @@ void M_CupSelectHandler(INT32 choice) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 55cfa3e80..461473bd9 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -484,7 +484,7 @@ void M_LevelSelected(INT16 add) F_WipeStartScreen(); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); diff --git a/src/p_setup.c b/src/p_setup.c index 6ae609536..86a57f2bb 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7555,7 +7555,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) F_WipeEndScreen(); S_StartSound(NULL, sfx_ruby1); - F_RunWipe(wipedefs[wipe_encore_toinvert], false, NULL, false, false); + F_RunWipe(wipe_encore_toinvert, wipedefs[wipe_encore_toinvert], false, NULL, false, false); // Hold on invert for extra effect. // (This define might be useful for other areas of code? Not sure) @@ -7584,7 +7584,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_encore_towhite], false, "FADEMAP1", false, true); // wiggle the screen during this! + F_RunWipe(wipe_encore_towhite, wipedefs[wipe_encore_towhite], false, "FADEMAP1", false, true); // wiggle the screen during this! // THEN fade to a black screen. F_WipeStartScreen(); @@ -7592,7 +7592,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); // Wait a bit longer. WAIT((3*TICRATE)/4); @@ -7600,8 +7600,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) else { // dedicated servers can call this now, to wait the appropriate amount of time for clients to wipe - F_RunWipe(wipedefs[wipe_encore_towhite], false, "FADEMAP1", false, true); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + F_RunWipe(wipe_encore_towhite, wipedefs[wipe_encore_towhite], false, "FADEMAP1", false, true); + F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); } } @@ -7625,6 +7625,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // But only if we didn't do the encore startup wipe if (!demo.rewinding && !reloadinggamestate) { + int wipetype = wipe_level_toblack; // Fade out music here. Deduct 2 tics so the fade volume actually reaches 0. // But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug. @@ -7654,6 +7655,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) if (ranspecialwipe != 2) S_StartSound(NULL, sfx_s3kaf); levelfadecol = 0; + wipetype = wipe_encore_towhite; } else if (encoremode) { @@ -7672,7 +7674,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) F_WipeEndScreen(); } - F_RunWipe(wipedefs[wipe_level_toblack], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); + F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); } /*if (!titlemapinaction) wipegamestate = GS_LEVEL;*/ From f82bdd878d6262068d34f1db5483d57d960029f2 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 27 Feb 2023 21:46:40 -0600 Subject: [PATCH 20/60] hwr2: remove now-unused software wiggle --- src/f_wipe.c | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/f_wipe.c b/src/f_wipe.c index 2068ac6d5..227573c46 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -366,51 +366,6 @@ void F_WipeEndScreen(void) #endif } -/** Wiggle post processor for encore wipes - */ -static void F_DoEncoreWiggle(UINT8 time) -{ - UINT8 *tmpscr = wipe_scr_start; - UINT8 *srcscr = wipe_scr; - angle_t disStart = (time * 128) & FINEMASK; - INT32 y, sine, newpix, scanline; - - for (y = 0; y < vid.height; y++) - { - sine = (FINESINE(disStart) * (time*12))>>FRACBITS; - scanline = y / vid.dupy; - if (scanline & 1) - sine = -sine; - newpix = abs(sine); - - if (sine < 0) - { - M_Memcpy(&tmpscr[(y*vid.width)+newpix], &srcscr[(y*vid.width)], vid.width-newpix); - - // Cleanup edge - while (newpix) - { - tmpscr[(y*vid.width)+newpix] = srcscr[(y*vid.width)]; - newpix--; - } - } - else - { - M_Memcpy(&tmpscr[(y*vid.width)], &srcscr[(y*vid.width) + sine], vid.width-newpix); - - // Cleanup edge - while (newpix) - { - tmpscr[(y*vid.width) + vid.width - newpix] = srcscr[(y*vid.width) + (vid.width-1)]; - newpix--; - } - } - - disStart += (time*8); //the offset into the displacement map, increment each game loop - disStart &= FINEMASK; //clip it to FINEMASK - } -} - /** Draw the stage title. */ void F_WipeStageTitle(void) From 43b88b9d2912e1ee94be3e478178f6652d8d7629 Mon Sep 17 00:00:00 2001 From: SteelT Date: Mon, 27 Feb 2023 23:11:03 -0500 Subject: [PATCH 21/60] Buff the damage given to battle monitors when in spring grease state --- src/objects/monitor.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/objects/monitor.c b/src/objects/monitor.c index 50d13ee3c..8f5be65eb 100644 --- a/src/objects/monitor.c +++ b/src/objects/monitor.c @@ -602,6 +602,12 @@ Obj_MonitorGetDamage damage = HEALTHFACTOR + (FixedMul(weight, HEALTHFACTOR) / 9); + + if (inflictor->player->tiregrease > 0) + { + damage *= 3; // Do 3x the damage if the player is in spring grease state + } + if (inflictor->scale > mapobjectscale) { damage = P_ScaleFromMap(damage, inflictor->scale); From 41be94ec5fbc2e8f055007c7742ad931b8fe4cde Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 27 Feb 2023 23:10:14 -0700 Subject: [PATCH 22/60] Use correct ziptic size parsing RA ghosts --- src/g_demo.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index c94386d31..174511e4d 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -1184,7 +1184,7 @@ void G_GhostTicker(void) for(g = ghosts, p = NULL; g; g = g->next) { // Skip normal demo data. - UINT8 ziptic = READUINT8(g->p); + UINT16 ziptic = READUINT8(g->p); UINT8 xziptic = 0; while (ziptic != DW_END) // Get rid of extradata stuff @@ -1225,12 +1225,14 @@ void G_GhostTicker(void) ziptic = READUINT8(g->p); } - ziptic = READUINT8(g->p); + ziptic = READUINT16(g->p); if (ziptic & ZT_FWD) g->p++; if (ziptic & ZT_TURNING) g->p += 2; + if (ziptic & ZT_ANGLE) + g->p += 2; if (ziptic & ZT_THROWDIR) g->p += 2; if (ziptic & ZT_BUTTONS) From 42fa2cf28628217d078b0e9a9f7474fb05f0c467 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 28 Feb 2023 19:20:31 -0600 Subject: [PATCH 23/60] hwr2: restore software CustomFadeScreen --- src/v_video.cpp | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/v_video.cpp b/src/v_video.cpp index 87a4ddfbe..b9ea539bb 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1570,8 +1570,6 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) // void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) { - (void)lump; - (void)strength; #ifdef HWRENDER if (rendermode != render_soft && rendermode != render_none) { @@ -1580,7 +1578,37 @@ void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) } #endif - // NOTE: This is not implementable in HWR2. + // TODO: fix this for Twodee + { + lumpnum_t lumpnum = LUMPERROR; + lighttable_t *clm = NULL; + + if (lump != NULL) + lumpnum = W_GetNumForName(lump); + else + return; + + if (lumpnum != LUMPERROR) + { + clm = static_cast(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8)); + W_ReadLump(lumpnum, clm); + + if (clm != NULL) + { + const UINT8 *fadetable = ((UINT8 *)clm + strength*256); + const UINT8 *deststop = 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. From 639a56bdfd9f2c36373c802d3ace2f73e7a73154 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 28 Feb 2023 19:49:17 -0600 Subject: [PATCH 24/60] hwr2: fix encore wipe to white --- src/p_setup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/p_setup.c b/src/p_setup.c index 86a57f2bb..4f5a07191 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7660,6 +7660,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) else if (encoremode) { levelfadecol = 0; + wipetype = wipe_encore_towhite; } else { From de00dd15fc13b8b961b348e682978c2959b5a667 Mon Sep 17 00:00:00 2001 From: SteelT Date: Tue, 28 Feb 2023 21:49:24 -0500 Subject: [PATCH 25/60] Fix building errors with webm, catch missing SRB2_CONFIG_ENABLE_WEBM_MOVIES condition --- CMakeLists.txt | 8 +++++--- thirdparty/CMakeLists.txt | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a83ed5f8c..cdbb760c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,9 +133,11 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}") find_package(SDL2 REQUIRED) find_package(CURL REQUIRED) find_package(GME REQUIRED) - find_package(VPX REQUIRED) - find_package(Vorbis REQUIRED) - find_package(VorbisEnc REQUIRED) + if (SRB2_CONFIG_ENABLE_WEBM_MOVIES) + find_package(VPX REQUIRED) + find_package(Vorbis REQUIRED) + find_package(VorbisEnc REQUIRED) + endif() endif() if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR}) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 0c77d8c1b..2dd78d8c0 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -24,10 +24,12 @@ include("cpm-fmt.cmake") include("cpm-imgui.cmake") include("cpm-acsvm.cmake") -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND SRB2_CONFIG_ENABLE_WEBM_MOVIES) - include("cpm-libvpx.cmake") - include("cpm-ogg.cmake") # libvorbis depends - include("cpm-libvorbis.cmake") +if (SRB2_CONFIG_ENABLE_WEBM_MOVIES) + if (NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") + include("cpm-libvpx.cmake") + include("cpm-ogg.cmake") # libvorbis depends + include("cpm-libvorbis.cmake") + endif() include("cpm-libwebm.cmake") include("cpm-libyuv.cmake") endif() From 36113f2d475bb2013867965a8cd545d7cddab04f Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 11:38:57 -0800 Subject: [PATCH 26/60] AngleDelta: return angle_t This function can return ANGLE_180, which when cast to a signed integer, is a negative value. --- src/tables.c | 2 +- src/tables.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tables.c b/src/tables.c index 8c4dc50e1..6a7b7645b 100644 --- a/src/tables.c +++ b/src/tables.c @@ -167,7 +167,7 @@ angle_t FixedAngle(fixed_t fa) return AngleAdj(cfa, cwf, ra); } -INT32 AngleDelta(angle_t a1, angle_t a2) +angle_t AngleDelta(angle_t a1, angle_t a2) { angle_t delta = a1 - a2; diff --git a/src/tables.h b/src/tables.h index ac3bfe8a5..f8947ab9e 100644 --- a/src/tables.h +++ b/src/tables.h @@ -110,7 +110,7 @@ FUNCMATH angle_t FixedAngle(fixed_t fa); // and with a factor, with +factor for (fa/factor) and -factor for (fa*factor) FUNCMATH angle_t FixedAngleC(fixed_t fa, fixed_t factor); // difference between two angle_t -FUNCMATH INT32 AngleDelta(angle_t a1, angle_t a2); +FUNCMATH angle_t AngleDelta(angle_t a1, angle_t a2); FUNCMATH INT32 AngleDeltaSigned(angle_t a1, angle_t a2); FUNCMATH float AngleToFloat(angle_t x); FUNCMATH angle_t FloatToAngle(float f); From ff15fc142b2ffad4e1f9033743671c48ca586079 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 11:44:36 -0800 Subject: [PATCH 27/60] Add FSIN/FCOS, shortcuts to FINESINE/FINECOSINE --- src/tables.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tables.h b/src/tables.h index f8947ab9e..1a1da82d3 100644 --- a/src/tables.h +++ b/src/tables.h @@ -27,7 +27,7 @@ extern "C" { #define FINEANGLES 8192 #define FINEMASK (FINEANGLES - 1) #define ANGLETOFINESHIFT 19 // 0x100000000 to 0x2000 -#define FINEANGLE_C(x) ((FixedAngle((x)*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK) // ((x*(ANGLE_45/45))>>ANGLETOFINESHIFT) & FINEMASK +#define ANGLETOFINE(x) (((x)>>ANGLETOFINESHIFT) & FINEMASK) // Effective size is 10240. extern fixed_t finesine[5*FINEANGLES/4]; @@ -136,6 +136,10 @@ void FM_Rotate(matrix_t *dest, angle_t angle, fixed_t x, fixed_t y, fixed_t z); #define FINECOSINE(n) (finecosine[n]>>(FINE_FRACBITS-FRACBITS)) #define FINETANGENT(n) (finetangent[n]>>(FINE_FRACBITS-FRACBITS)) +// FSIN(ANGLE_90) = FRACUNIT +#define FSIN(n) FINESINE(ANGLETOFINE(n)) +#define FCOS(n) FINECOSINE(ANGLETOFINE(n)) + #ifdef __cplusplus } // extern "C" #endif From 290d3fd3e8be55af10e4a13f20adad8441a7a5d0 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 19:06:25 -0800 Subject: [PATCH 28/60] Do not selectively reset mobj interpolation state at the beginning of P_MobjThinker Fixes P_MoveOrigin not interpolating if used before P_MobjThinker, i.e. during player think. These are reset at the start of a tic anyway, before anything else, with R_UpdateMobjInterpolators. --- src/p_mobj.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index d9920a928..5c2a8f724 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9743,14 +9743,6 @@ void P_MobjThinker(mobj_t *mobj) I_Assert(mobj != NULL); I_Assert(!P_MobjWasRemoved(mobj)); - // Set old position (for interpolation) - mobj->old_x = mobj->x; - mobj->old_y = mobj->y; - mobj->old_z = mobj->z; - mobj->old_angle = mobj->angle; - mobj->old_pitch = mobj->pitch; - mobj->old_roll = mobj->roll; - // Remove dead target/tracer. if (mobj->target && P_MobjWasRemoved(mobj->target)) P_SetTarget(&mobj->target, NULL); From 49bb2a377eda5e93da7ac3dd227c363f52854708 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 16:23:32 -0800 Subject: [PATCH 29/60] Add P_SpawnItemLine, spawn a row of mapthings between two points --- src/p_mobj.c | 12 ++++++++++++ src/p_mobj.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/p_mobj.c b/src/p_mobj.c index 5c2a8f724..b9fa47a36 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13726,6 +13726,18 @@ void P_SpawnItemPattern(mapthing_t *mthing) } } +void P_SpawnItemLine(mapthing_t *mt1, mapthing_t *mt2) +{ + const mobjtype_t type = P_GetMobjtype(mt1->type); + const fixed_t diameter = 2 * FixedMul(mobjinfo[type].radius, mapobjectscale); + const fixed_t dx = (mt2->x - mt1->x) * FRACUNIT; + const fixed_t dy = (mt2->y - mt1->y) * FRACUNIT; + const fixed_t dist = FixedHypot(dx, dy); + const angle_t angle = R_PointToAngle2(0, 0, dx, dy); + + P_SpawnSingularItemRow(mt1, type, (dist / diameter) + 1, diameter, 0, AngleFixed(angle) / FRACUNIT); +} + // // P_CheckMissileSpawn // Moves the missile forward a bit and possibly explodes it right there. diff --git a/src/p_mobj.h b/src/p_mobj.h index dd448841e..d5dc67a3e 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -530,6 +530,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt mobj_t *P_SpawnMapThing(mapthing_t *mthing); void P_SpawnHoop(mapthing_t *mthing); void P_SpawnItemPattern(mapthing_t *mthing); +void P_SpawnItemLine(mapthing_t *mt1, mapthing_t *mt2); void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle); void P_SpawnPrecipitation(void); void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter); From e5a533544d5014946845900521e6d7cce1845ca1 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 12:03:26 -0800 Subject: [PATCH 30/60] Add sonicloopvars_t to player_t --- src/d_player.h | 12 ++++++++++++ src/p_saveg.c | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/d_player.h b/src/d_player.h index 12cab4a9a..298c65485 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -374,6 +374,16 @@ struct itemroulette_t boolean eggman; }; +// player_t struct for loop state +typedef struct { + fixed_t radius; + fixed_t revolution, min_revolution, max_revolution; + angle_t yaw; + vector3_t origin; + vector2_t shift; + boolean flip; +} sonicloopvars_t; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -668,6 +678,8 @@ struct player_t #ifdef HWRENDER fixed_t fovadd; // adjust FOV for hw rendering #endif + + sonicloopvars_t loop; }; #ifdef __cplusplus diff --git a/src/p_saveg.c b/src/p_saveg.c index 55fac75f5..fc9d7daf4 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -465,6 +465,19 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].itemRoulette.tics); WRITEUINT32(save->p, players[i].itemRoulette.elapsed); WRITEUINT8(save->p, players[i].itemRoulette.eggman); + + // sonicloopsvars_t + WRITEFIXED(save->p, players[i].loop.radius); + WRITEFIXED(save->p, players[i].loop.revolution); + WRITEFIXED(save->p, players[i].loop.min_revolution); + WRITEFIXED(save->p, players[i].loop.max_revolution); + WRITEANGLE(save->p, players[i].loop.yaw); + WRITEFIXED(save->p, players[i].loop.origin.x); + WRITEFIXED(save->p, players[i].loop.origin.y); + WRITEFIXED(save->p, players[i].loop.origin.z); + WRITEFIXED(save->p, players[i].loop.shift.x); + WRITEFIXED(save->p, players[i].loop.shift.y); + WRITEUINT8(save->p, players[i].loop.flip); } } @@ -834,6 +847,19 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].itemRoulette.elapsed = (tic_t)READUINT32(save->p); players[i].itemRoulette.eggman = (boolean)READUINT8(save->p); + // sonicloopsvars_t + players[i].loop.radius = READFIXED(save->p); + players[i].loop.revolution = READFIXED(save->p); + players[i].loop.min_revolution = READFIXED(save->p); + players[i].loop.max_revolution = READFIXED(save->p); + players[i].loop.yaw = READANGLE(save->p); + players[i].loop.origin.x = READFIXED(save->p); + players[i].loop.origin.y = READFIXED(save->p); + players[i].loop.origin.z = READFIXED(save->p); + players[i].loop.shift.x = READFIXED(save->p); + players[i].loop.shift.y = READFIXED(save->p); + players[i].loop.flip = READUINT8(save->p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } From 37edae71fa57f917144b3eb2a37d642f6013d0f9 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 11:50:55 -0800 Subject: [PATCH 31/60] Clean up some code in p_setup.c --- src/p_setup.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index 0c8ddef6b..d49e82348 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -700,10 +700,13 @@ static void P_SpawnMapThings(boolean spawnemblems) for (i = 0, mt = mapthings; i < nummapthings; i++, mt++) { - if (mt->type == 1700 // MT_AXIS - || mt->type == 1701 // MT_AXISTRANSFER - || mt->type == 1702) // MT_AXISTRANSFERLINE - continue; // These were already spawned + switch (mt->type) + { + case 1700: // MT_AXIS + case 1701: // MT_AXISTRANSFER + case 1702: // MT_AXISTRANSFERLINE + continue; // These were already spawned + } if (mt->type == mobjinfo[MT_BATTLECAPSULE].doomednum) continue; // This will spawn later @@ -5952,6 +5955,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[1] |= TMBOT_FORCEDIR; lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset / FRACUNIT; + break; default: break; } From b3511a9149f853c3bc8d36858306a1215b69bf26 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 16:19:41 -0800 Subject: [PATCH 32/60] Player orbiting physics --- src/CMakeLists.txt | 1 + src/p_local.h | 4 + src/p_loop.c | 180 +++++++++++++++++++++++++++++++++++++++++++++ src/p_map.c | 18 ++++- src/p_mobj.c | 6 +- src/p_user.c | 5 ++ 6 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/p_loop.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 898fcc789..8bd399a1a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_floor.c p_inter.c p_lights.c + p_loop.c p_map.c p_maputl.c p_mobj.c diff --git a/src/p_local.h b/src/p_local.h index bf847fbca..12362a548 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -193,6 +193,10 @@ boolean P_AutoPause(void); void P_ElementalFire(player_t *player, boolean cropcircle); void P_SpawnSkidDust(player_t *player, fixed_t radius, boolean sound); +void P_HaltPlayerOrbit(player_t *player); +void P_ExitPlayerOrbit(player_t *player); +boolean P_PlayerOrbit(player_t *player); + void P_MovePlayer(player_t *player); void P_PlayerThink(player_t *player); void P_PlayerAfterThink(player_t *player); diff --git a/src/p_loop.c b/src/p_loop.c new file mode 100644 index 000000000..e2d838f31 --- /dev/null +++ b/src/p_loop.c @@ -0,0 +1,180 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Kart Krew +// +// 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 p_loop.c +/// \brief Sonic loop physics + +#include "doomdef.h" +#include "d_player.h" +#include "k_kart.h" +#include "p_local.h" +#include "p_setup.h" +#include "p_slopes.h" +#include "r_main.h" + +static inline angle_t +get_pitch (fixed_t revolution) +{ + return FixedAngle((revolution & FRACMASK) * 360); +} + +static inline fixed_t +get_shift_curve (const sonicloopvars_t *s) +{ + const angle_t th = get_pitch(FixedDiv( + s->revolution - s->min_revolution, + s->max_revolution - s->min_revolution)); + + // XY shift is transformed on wave scale; less movement + // at start and end of rotation, more halfway. + return FSIN((th / 2) - ANGLE_90); +} + +void P_HaltPlayerOrbit(player_t *player) +{ + // see P_PlayerOrbit + player->mo->flags &= ~(MF_NOCLIPHEIGHT); + + player->loop.radius = 0; +} + +void P_ExitPlayerOrbit(player_t *player) +{ + sonicloopvars_t *s = &player->loop; + + angle_t pitch = get_pitch(s->revolution); + angle_t yaw = s->yaw; + + fixed_t co, si; + fixed_t speed; + + if (s->radius < 0) + { + pitch += ANGLE_180; + } + + co = FCOS(pitch); + si = FSIN(pitch); + + speed = FixedMul(co, player->speed); + + P_InstaThrust(player->mo, yaw, speed); + + player->mo->momz = FixedMul(si, player->speed); + + if (speed < 0) + { + yaw += ANGLE_180; + } + + // excludes only extremely vertical angles + if (abs(co) * 4 > abs(si)) + { + P_SetPlayerAngle(player, yaw); + } + + if (s->flip) + { + player->mo->eflags ^= MFE_VERTICALFLIP; + player->mo->flags2 ^= MF2_OBJECTFLIP; + + P_SetPitchRoll(player->mo, + pitch + ANGLE_180, s->yaw); + } + + // tiregrease gives less friction, extends momentum + player->tiregrease = TICRATE; + + P_HaltPlayerOrbit(player); +} + +boolean P_PlayerOrbit(player_t *player) +{ + sonicloopvars_t *s = &player->loop; + + angle_t pitch; + + fixed_t xy, z; + fixed_t xs, ys; + + fixed_t step, th, left; + + fixed_t grav; + + if (s->radius == 0) + { + return false; + } + + grav = abs(P_GetMobjGravity(player->mo)); + + // Lose speed on the way up. revolution = 0.5 always + // points straight up. + if (abs(s->revolution & FRACMASK) < FRACUNIT/2) + { + player->speed -= grav; + } + else + { + player->speed += 4 * grav; + } + + pitch = get_pitch(s->revolution); + + xy = FixedMul(abs(s->radius), FSIN(pitch)); + z = FixedMul(abs(s->radius), -(FCOS(pitch))); + + th = get_shift_curve(s); + + xs = FixedMul(s->shift.x, th); + ys = FixedMul(s->shift.y, th); + + xs += FixedMul(xy, FCOS(s->yaw)); + ys += FixedMul(xy, FSIN(s->yaw)); + + P_MoveOrigin(player->mo, + s->origin.x + xs, + s->origin.y + ys, + s->origin.z + z); + + // Match rollangle to revolution + P_SetPitchRoll(player->mo, + s->radius < 0 ? (ANGLE_180 + pitch) : pitch, + s->yaw); + + // circumfrence = (2r)PI + step = FixedDiv(player->speed, + FixedMul(s->radius, M_TAU_FIXED)); + + left = (s->max_revolution - s->revolution); + + if (abs(left) < abs(step)) + { + P_ExitPlayerOrbit(player); + + return false; + } + + // If player slows down by too much, throw them out of + // the loop in a tumble. + if (player->speed < player->mo->scale) + { + P_HaltPlayerOrbit(player); + K_StumblePlayer(player); + + return false; + } + + s->revolution += step; + + // We need to not clip the ground. It sucks but setting + // this flag is the only way to do that. + player->mo->flags |= (MF_NOCLIPHEIGHT); + + return true; +} diff --git a/src/p_map.c b/src/p_map.c index 1f7051c10..8c5751d2a 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2674,6 +2674,22 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY) return maxstep; } +static boolean P_UsingStepUp(mobj_t *thing) +{ + if (thing->flags & MF_NOCLIP) + { + return false; + } + + // orbits have no collision + if (thing->player && thing->player->loop.radius) + { + return false; + } + + return true; +} + static boolean increment_move ( mobj_t * thing, @@ -2734,7 +2750,7 @@ increment_move // copy into the spechitint buffer from spechit spechitint_copyinto(); - if (!(thing->flags & MF_NOCLIP)) + if (P_UsingStepUp(thing)) { // All things are affected by their scale. fixed_t maxstep = P_GetThingStepUp(thing, tryx, tryy); diff --git a/src/p_mobj.c b/src/p_mobj.c index b9fa47a36..1fb36e854 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3979,7 +3979,8 @@ static void P_CheckFloatbobPlatforms(mobj_t *mobj) static void P_SquishThink(mobj_t *mobj) { if (!(mobj->flags & MF_NOSQUISH) && - !(mobj->eflags & MFE_SLOPELAUNCHED)) + !(mobj->eflags & MFE_SLOPELAUNCHED) && + !(mobj->player && mobj->player->loop.radius != 0)) { K_Squish(mobj); } @@ -4002,7 +4003,8 @@ static void P_PlayerMobjThinker(mobj_t *mobj) // Zoom tube if ((mobj->player->carry == CR_ZOOMTUBE && mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) - || mobj->player->respawn.state == RESPAWNST_MOVE) + || mobj->player->respawn.state == RESPAWNST_MOVE + || mobj->player->loop.radius != 0) { P_HitSpecialLines(mobj, mobj->x, mobj->y, mobj->momx, mobj->momy); P_UnsetThingPosition(mobj); diff --git a/src/p_user.c b/src/p_user.c index 223af8f47..7826d4071 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4227,6 +4227,11 @@ void P_PlayerThink(player_t *player) P_DoZoomTube(player); player->rmomx = player->rmomy = 0; } + else if (player->loop.radius != 0) + { + P_PlayerOrbit(player); + player->rmomx = player->rmomy = 0; + } else { // Move around. From 583ef41ac9fa1fcf4ec5c8b22d8114d885749270 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 11:46:37 -0800 Subject: [PATCH 33/60] Add MT_LOOPENDPOINT, MT_LOOPCENTERPOINT Doomednum 2020, 2021 --- src/deh_tables.c | 3 +++ src/info.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ src/info.h | 3 +++ 3 files changed, 60 insertions(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index 07c69f687..ab5e7959c 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5664,6 +5664,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_SPECIAL_UFO", "MT_SPECIAL_UFO_PIECE", + + "MT_LOOPENDPOINT", + "MT_LOOPCENTERPOINT", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 4807869e6..d0509e731 100644 --- a/src/info.c +++ b/src/info.c @@ -29311,6 +29311,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags S_NULL // raisestate }, + + { // MT_LOOPENDPOINT + 2020, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + MAXRADIUS, // radius + 2*MAXRADIUS, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + + { // MT_LOOPCENTERPOINT + 2021, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 48*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index 1bac859b1..02e194cb6 100644 --- a/src/info.h +++ b/src/info.h @@ -6736,6 +6736,9 @@ typedef enum mobj_type MT_SPECIAL_UFO, MT_SPECIAL_UFO_PIECE, + MT_LOOPENDPOINT, + MT_LOOPCENTERPOINT, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES From 7738d6bb7c5478d8f0f4b6dc71290ffc5bcb44ae Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 2 Nov 2022 17:10:59 -0700 Subject: [PATCH 34/60] Activate loop endpoints --- src/k_objects.h | 4 + src/objects/CMakeLists.txt | 1 + src/objects/loops.c | 211 +++++++++++++++++++++++++++++++++++++ src/p_inter.c | 4 + src/p_mobj.c | 5 + 5 files changed, 225 insertions(+) create mode 100644 src/objects/loops.c diff --git a/src/k_objects.h b/src/k_objects.h index 05debb889..830c9e769 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -92,6 +92,10 @@ boolean Obj_ItemSpotIsAvailable(const mobj_t *spot); void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); void Obj_ItemSpotUpdate(mobj_t *spot); +/* Loops */ +void Obj_InitLoopCenter(mobj_t *center); +void Obj_LoopEndpointCollide(mobj_t *special, mobj_t *toucher); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 440f700de..ca8680205 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE ufo.c monitor.c item-spot.c + loops.c ) diff --git a/src/objects/loops.c b/src/objects/loops.c new file mode 100644 index 000000000..a8838e3df --- /dev/null +++ b/src/objects/loops.c @@ -0,0 +1,211 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James R. +// Copyright (C) 2023 by Kart Krew +// +// 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 loop-endpoint.c +/// \brief Sonic loops, start and end points + +#include "../doomdef.h" +#include "../k_kart.h" +#include "../taglist.h" +#include "../p_local.h" +#include "../p_setup.h" +#include "../p_spec.h" +#include "../r_main.h" +#include "../k_objects.h" + +#define end_anchor(o) ((o)->target) + +#define center_max_revolution(o) ((o)->threshold) +#define center_alpha(o) ((o)->target) +#define center_beta(o) ((o)->tracer) + +static inline boolean +center_has_flip (const mobj_t *center) +{ + return (center->flags2 & MF2_AMBUSH) == MF2_AMBUSH; +} + +static inline void +center_set_flip +( mobj_t * center, + boolean mode) +{ + center->flags2 = (center->flags2 & ~(MF2_AMBUSH)) | + ((mode != false) * MF2_AMBUSH); +} + +#define anchor_center(o) ((o)->target) +#define anchor_other(o) ((o)->tracer) +#define anchor_type(o) ((o)->reactiontime) + +static void +set_shiftxy +( player_t * player, + const mobj_t * a) +{ + const mobj_t *b = anchor_other(a); + + const fixed_t dx = (b->x - a->x); + const fixed_t dy = (b->y - a->y); + + const angle_t th = + (R_PointToAngle2(0, 0, dx, dy) - a->angle); + + const fixed_t adj = FixedMul( + abs(FCOS(AbsAngle(th - ANGLE_90))), + FixedHypot(dx, dy)) / 2; + + vector2_t *xy = &player->loop.shift; + + xy->x = FixedMul(FSIN(a->angle), adj); + xy->y = FixedMul(FCOS(a->angle), adj); +} + +static void +measure_clock +( const mobj_t * center, + const mobj_t * anchor, + angle_t * pitch, + fixed_t * radius) +{ + const fixed_t dx = (anchor->x - center->x); + const fixed_t dy = (anchor->y - center->y); + const fixed_t dz = (anchor->z - center->z); + + /* Translate the anchor point to be along a center line. + This makes the horizontal position one dimensional + relative to the center point. */ + const fixed_t xy = ( + FixedMul(dx, FCOS(anchor->angle)) + + FixedMul(dy, FSIN(anchor->angle))); + + /* The 3d position of the anchor point is then reduced to + two axes and can be measured as an angle. */ + *pitch = R_PointToAngle2(0, 0, xy, dz) + ANGLE_90; + *radius = FixedHypot(xy, dz); +} + +static boolean +moving_toward_gate +( const player_t * player, + const mobj_t * anchor, + angle_t pitch) +{ + const fixed_t + x = player->mo->momx, + y = player->mo->momy, + z = player->mo->momz, + + zx = FixedMul(FCOS(anchor->angle), z), + zy = FixedMul(FSIN(anchor->angle), z), + + co = abs(FCOS(pitch)), + si = abs(FSIN(pitch)), + + dx = FixedMul(co, x) + FixedMul(si, zx), + dy = FixedMul(co, y) + FixedMul(si, zy); + + return AngleDelta(anchor->angle, + R_PointToAngle2(0, 0, dx, dy)) < ANG60; +} + +static SINT8 +get_binary_direction +( angle_t pitch, + mobj_t * toucher) +{ + const fixed_t si = FSIN(pitch); + + if (abs(si) < abs(FCOS(pitch))) + { + // pitch = 0 points downward so offset 90 degrees + // clockwise so 180 occurs at horizon + return ((pitch + ANGLE_90) < ANGLE_180) ? 1 : -(1); + } + else + { + return intsign(si) * P_MobjFlip(toucher); + } +} + +void +Obj_InitLoopCenter (mobj_t *center) +{ + const mapthing_t *mt = center->spawnpoint; + + center_max_revolution(center) = mt->args[1] * FRACUNIT / 360; + center_set_flip(center, mt->args[0]); +} + +void +Obj_LoopEndpointCollide +( mobj_t * end, + mobj_t * toucher) +{ + player_t *player = toucher->player; + sonicloopvars_t *s = &player->loop; + + mobj_t *anchor = end_anchor(end); + mobj_t *center = anchor ? anchor_center(anchor) : NULL; + + angle_t pitch; + fixed_t radius; + + /* predict movement for a smooth transition */ + const fixed_t px = toucher->x + toucher->momx; + const fixed_t py = toucher->y + toucher->momy; + + SINT8 flip; + + if (P_MobjWasRemoved(center)) + { + return; + } + + if (player->loop.radius != 0) + { + return; + } + + measure_clock(center, anchor, &pitch, &radius); + + if (!moving_toward_gate(player, anchor, pitch)) + { + return; + } + + if (!P_MobjWasRemoved(anchor_other(anchor))) + { + set_shiftxy(player, anchor); + } + + flip = get_binary_direction(pitch, toucher); + + s->yaw = anchor->angle; + + s->origin.x = center->x - (anchor->x - px); + s->origin.y = center->y - (anchor->y - py); + s->origin.z = center->z; + + s->radius = radius * flip; + s->revolution = AngleFixed(pitch) / 360; + + s->min_revolution = s->revolution; + s->max_revolution = s->revolution + + center_max_revolution(center) * flip; + + s->flip = center_has_flip(center); + + player->speed = + 3 * (player->speed + toucher->momz) / 2; + + /* cancel the effects of K_Squish */ + toucher->spritexscale = FRACUNIT; + toucher->spriteyscale = FRACUNIT; +} diff --git a/src/p_inter.c b/src/p_inter.c index 6e053a405..73b018e92 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -601,6 +601,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } return; + case MT_LOOPENDPOINT: + Obj_LoopEndpointCollide(special, toucher); + return; + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 1fb36e854..2d128f0cd 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13327,6 +13327,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean mobj->reactiontime++; break; } + case MT_LOOPCENTERPOINT: + { + Obj_InitLoopCenter(mobj); + break; + } default: break; } From 8bd83b69c37323d95cbb58dbc655c784309aa315 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 21 Nov 2022 16:29:39 -0800 Subject: [PATCH 35/60] Spawn MT_LOOPENDPOINT and MT_LOOPCENTERPOINT mapthings --- src/k_objects.h | 5 ++++ src/objects/loops.c | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/p_mobj.c | 31 +++++++++++++++++++- src/p_setup.c | 63 ++++++++++++++++++++++++++++++++++++++++ src/p_spec.h | 6 ++++ 5 files changed, 174 insertions(+), 1 deletion(-) diff --git a/src/k_objects.h b/src/k_objects.h index 830c9e769..922764c94 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -2,6 +2,8 @@ #ifndef k_objects_H #define k_objects_H +#include "taglist.h" + #ifdef __cplusplus extern "C" { #endif @@ -93,7 +95,10 @@ void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); void Obj_ItemSpotUpdate(mobj_t *spot); /* Loops */ +mobj_t *Obj_FindLoopCenter(const mtag_t tag); +void Obj_InitLoopEndpoint(mobj_t *end, mobj_t *anchor); void Obj_InitLoopCenter(mobj_t *center); +void Obj_LinkLoopAnchor(mobj_t *anchor, mobj_t *center, UINT8 type); void Obj_LoopEndpointCollide(mobj_t *special, mobj_t *toucher); #ifdef __cplusplus diff --git a/src/objects/loops.c b/src/objects/loops.c index a8838e3df..dd9ea7c3b 100644 --- a/src/objects/loops.c +++ b/src/objects/loops.c @@ -91,6 +91,21 @@ measure_clock *radius = FixedHypot(xy, dz); } +static void +crisscross +( mobj_t * anchor, + mobj_t ** target_p, + mobj_t ** other_p) +{ + P_SetTarget(target_p, anchor); + + if (!P_MobjWasRemoved(*other_p)) + { + P_SetTarget(&anchor_other(anchor), *other_p); + P_SetTarget(&anchor_other(*other_p), anchor); + } +} + static boolean moving_toward_gate ( const player_t * player, @@ -134,6 +149,32 @@ get_binary_direction } } +mobj_t * +Obj_FindLoopCenter (const mtag_t tag) +{ + INT32 i; + + TAG_ITER_THINGS(tag, i) + { + mapthing_t *mt = &mapthings[i]; + + if (mt->type == mobjinfo[MT_LOOPCENTERPOINT].doomednum) + { + return mt->mobj; + } + } + + return NULL; +} + +void +Obj_InitLoopEndpoint +( mobj_t * end, + mobj_t * anchor) +{ + P_SetTarget(&end_anchor(end), anchor); +} + void Obj_InitLoopCenter (mobj_t *center) { @@ -143,6 +184,35 @@ Obj_InitLoopCenter (mobj_t *center) center_set_flip(center, mt->args[0]); } +void +Obj_LinkLoopAnchor +( mobj_t * anchor, + mobj_t * center, + UINT8 type) +{ + P_SetTarget(&anchor_center(anchor), center); + + anchor_type(anchor) = type; + + if (!P_MobjWasRemoved(center)) + { + switch (type) + { + case TMLOOP_ALPHA: + crisscross(anchor, + ¢er_alpha(center), + ¢er_beta(center)); + break; + + case TMLOOP_BETA: + crisscross(anchor, + ¢er_beta(center), + ¢er_alpha(center)); + break; + } + } +} + void Obj_LoopEndpointCollide ( mobj_t * end, diff --git a/src/p_mobj.c b/src/p_mobj.c index 2d128f0cd..232812765 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13552,6 +13552,11 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi angle_t angle = FixedAngle(fixedangle << FRACBITS); angle_t fineangle = (angle >> ANGLETOFINESHIFT) & FINEMASK; + boolean isloopend = (mthing->type == mobjinfo[MT_LOOPENDPOINT].doomednum); + mobj_t *loopanchor; + + boolean inclusive = isloopend; + for (r = 0; r < numitemtypes; r++) { dummything = *mthing; @@ -13570,6 +13575,21 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi } z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale); + if (isloopend) + { + const fixed_t length = (numitems - 1) * horizontalspacing / 2; + + mobj_t *loopcenter = Obj_FindLoopCenter(Tag_FGet(&mthing->tags)); + + // Spawn the anchor at the middle point of the line + loopanchor = P_SpawnMobjFromMapThing(&dummything, + x + FixedMul(length, FINECOSINE(fineangle)), + y + FixedMul(length, FINESINE(fineangle)), + z, MT_LOOPCENTERPOINT); + + Obj_LinkLoopAnchor(loopanchor, loopcenter, mthing->args[0]); + } + for (r = 0; r < numitems; r++) { mobjtype_t itemtype = itemtypes[r % numitemtypes]; @@ -13577,15 +13597,24 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi continue; dummything.type = mobjinfo[itemtype].doomednum; + if (inclusive) + mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); + x += FixedMul(horizontalspacing, FINECOSINE(fineangle)); y += FixedMul(horizontalspacing, FINESINE(fineangle)); z += (mthing->options & MTF_OBJECTFLIP) ? -verticalspacing : verticalspacing; - mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); + if (!inclusive) + mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); if (!mobj) continue; + if (isloopend) + { + Obj_InitLoopEndpoint(mobj, loopanchor); + } + mobj->spawnpoint = NULL; } } diff --git a/src/p_setup.c b/src/p_setup.c index d49e82348..a348b4999 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -677,11 +677,26 @@ void P_ReloadRings(void) } } +static int cmp_loopends(const void *a, const void *b) +{ + const mapthing_t + *mt1 = *(const mapthing_t*const*)a, + *mt2 = *(const mapthing_t*const*)b; + + // weighted sorting; tag takes precedence over type + return + intsign(Tag_FGet(&mt1->tags) - Tag_FGet(&mt2->tags)) * 2 + + intsign(mt1->args[0] - mt2->args[0]); +} + static void P_SpawnMapThings(boolean spawnemblems) { size_t i; mapthing_t *mt; + mapthing_t **loopends; + size_t num_loopends = 0; + // Spawn axis points first so they are at the front of the list for fast searching. for (i = 0, mt = mapthings; i < nummapthings; i++, mt++) { @@ -690,14 +705,22 @@ static void P_SpawnMapThings(boolean spawnemblems) case 1700: // MT_AXIS case 1701: // MT_AXISTRANSFER case 1702: // MT_AXISTRANSFERLINE + case 2021: // MT_LOOPCENTERPOINT mt->mobj = NULL; P_SpawnMapThing(mt); break; + case 2020: // MT_LOOPENDPOINT + num_loopends++; + break; default: break; } } + Z_Malloc(num_loopends * sizeof *loopends, PU_STATIC, + &loopends); + num_loopends = 0; + for (i = 0, mt = mapthings; i < nummapthings; i++, mt++) { switch (mt->type) @@ -705,6 +728,7 @@ static void P_SpawnMapThings(boolean spawnemblems) case 1700: // MT_AXIS case 1701: // MT_AXISTRANSFER case 1702: // MT_AXISTRANSFERLINE + case 2021: // MT_LOOPCENTERPOINT continue; // These were already spawned } @@ -716,6 +740,13 @@ static void P_SpawnMapThings(boolean spawnemblems) mt->mobj = NULL; + if (mt->type == mobjinfo[MT_LOOPENDPOINT].doomednum) + { + loopends[num_loopends] = mt; + num_loopends++; + continue; + } + if (mt->type >= 600 && mt->type <= 611) // item patterns P_SpawnItemPattern(mt); else if (mt->type == 1713) // hoops @@ -723,6 +754,25 @@ static void P_SpawnMapThings(boolean spawnemblems) else // Everything else P_SpawnMapThing(mt); } + + qsort(loopends, num_loopends, sizeof *loopends, + cmp_loopends); + + for (i = 1; i < num_loopends; ++i) + { + mapthing_t + *mt1 = loopends[i - 1], + *mt2 = loopends[i]; + + if (Tag_FGet(&mt1->tags) == Tag_FGet(&mt2->tags) && + mt1->args[0] == mt2->args[0]) + { + P_SpawnItemLine(mt1, mt2); + i++; + } + } + + Z_Free(loopends); } // Experimental groovy write function! @@ -3931,6 +3981,8 @@ static void P_AddBinaryMapTags(void) case 292: case 294: case 780: + case 2020: // MT_LOOPENDPOINT + case 2021: // MT_LOOPCENTERPOINT Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo); break; default: @@ -6736,6 +6788,17 @@ static void P_ConvertBinaryThingTypes(void) mapthings[i].args[2] |= TMICF_INVERTSIZE; } break; + case 2020: // MT_LOOPENDPOINT + { + mapthings[i].args[0] = + mapthings[i].options & MTF_AMBUSH ? + TMLOOP_BETA : TMLOOP_ALPHA; + break; + } + case 2021: // MT_LOOPCENTERPOINT + mapthings[i].args[0] = (mapthings[i].options & MTF_AMBUSH) == MTF_AMBUSH; + mapthings[i].args[1] = mapthings[i].angle; + break; case 2050: // MT_DUELBOMB mapthings[i].args[1] = !!(mapthings[i].options & MTF_AMBUSH); break; diff --git a/src/p_spec.h b/src/p_spec.h index 6ccb328cd..908046936 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -513,6 +513,12 @@ typedef enum TMBOT_FORCEDIR = 1<<2, } textmapbotcontroller_t; +typedef enum +{ + TMLOOP_ALPHA = 0, + TMLOOP_BETA = 1, +} textmaploopendpointtype_t; + // GETSECSPECIAL (specialval, section) // // Pulls out the special # from a particular section. From 28b3006efd338e1fdad9ddd0c9afb62c89a18f71 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 13:41:25 +0000 Subject: [PATCH 36/60] K_DirectorSwitch: Don't let forced switches cause a G_ResetView update when directorinfo.active is false --- src/k_director.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/k_director.c b/src/k_director.c index b1513672c..dd2f55ad6 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -110,16 +110,16 @@ static boolean K_CanSwitchDirector(void) return false; } - if (!directorinfo.active) - { - return false; - } - return true; } static void K_DirectorSwitch(INT32 player, boolean force) { + if (!directorinfo.active) + { + return; + } + if (P_IsDisplayPlayer(&players[player])) { return; From 64792f30a2087a63077ac097185e82a903c5dbb9 Mon Sep 17 00:00:00 2001 From: Lat Date: Wed, 1 Mar 2023 15:48:40 +0100 Subject: [PATCH 37/60] Fix using cecho instead of tcecho for 1 line --- src/hu_stuff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index a68f4857b..83d77cb44 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2688,7 +2688,7 @@ void HU_DoTitlecardCEcho(const char *msg) strncpy(tcechotext, msg, sizeof(tcechotext)); strncat(tcechotext, "\\", sizeof(tcechotext) - strlen(tcechotext) - 1); - cechotext[sizeof(tcechotext) - 1] = '\0'; + tcechotext[sizeof(tcechotext) - 1] = '\0'; tcechotimer = 1; tcechoduration = TICRATE*6 + strlen(tcechotext); } \ No newline at end of file From 739d81f403c2187ebc07e6785f49f0d21976e8af Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 15:00:35 +0000 Subject: [PATCH 38/60] G_BuildTiccmd: Fix not being able to use item button to toggle PF_WANTSTOJOIN --- src/g_game.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 9dc0b2fc8..be27e7360 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1179,9 +1179,18 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) K_ToggleDirector(false); } - if (M_MenuButtonPressed(forplayer, MBT_R)) + if (player->spectator == true) { - K_ToggleDirector(true); + // duplication of fire + if (G_PlayerInputDown(forplayer, gc_item, 0)) + { + cmd->buttons |= BT_ATTACK; + } + + if (M_MenuButtonPressed(forplayer, MBT_R)) + { + K_ToggleDirector(true); + } } goto aftercmdinput; From 5ef24e1a3ce445136131139c8757818532e60298 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 1 Mar 2023 07:23:56 -0800 Subject: [PATCH 39/60] Fix flashing tics flickering animation for light snake --- src/k_respawn.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_respawn.c b/src/k_respawn.c index 0997c2b40..4d3675490 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -344,7 +344,10 @@ static void K_MovePlayerToRespawnPoint(player_t *player) player->mo->momx = player->mo->momy = player->mo->momz = 0; - player->flashing = 2; + // 3 because this timer counts down afterward, in + // P_PlayerThink. flashing must be > 1 after it has + // counted down in order to flicker the player sprite. + player->flashing = 3; //player->nocontrol = max(2, player->nocontrol); if (leveltime % 8 == 0 && !mapreset) From 6e5a5bf92d66dde3eeb7462e180934e63a3de00c Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 1 Mar 2023 07:25:14 -0800 Subject: [PATCH 40/60] Apply flashing tics to RESPAWNST_DROP Applies flashing tics to the entire respawn state so you don't become vulnerable once you reach the waypoint. --- src/k_respawn.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_respawn.c b/src/k_respawn.c index 4d3675490..cb08408a2 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -816,6 +816,7 @@ void K_RespawnChecker(player_t *player) return; case RESPAWNST_DROP: player->mo->momx = player->mo->momy = 0; + player->flashing = 3; if (player->respawn.timer > 0) { player->mo->momz = 0; From 0cd260eed177c0943ed5145d2204173fc969d76a Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 1 Mar 2023 07:25:54 -0800 Subject: [PATCH 41/60] Don't let death type damage kill respawning player This includes death pits and instakill sectors. Does not affect direct P_KillMobj calls. --- src/p_inter.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/p_inter.c b/src/p_inter.c index 6e053a405..09f6349d6 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1911,6 +1911,12 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, { (void)source; + if (player->respawn.state != RESPAWNST_NONE) + { + K_DoInstashield(player); + return false; + } + if (!player->exiting && specialstageinfo.valid == true) { player->pflags |= PF_NOCONTEST; From 93f151fec92767cd0173204ce5403eba86f73165 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 15:27:48 +0000 Subject: [PATCH 42/60] Director HUD: Do a bit of cleanup - Add "Join" on L button. - "Becomes ". . .", "Cancel join", and has "[i/n]" appended (where n is cap and i is number of players in game) - Add colorisation when Director is active - Make an attempt at fixing splitscreen (I wasn't able to get director to ACTIVATE in splitscreen with two local clients so it's frustratingly untested) --- src/k_hud.c | 63 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index d53a39d95..683040065 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4639,16 +4639,26 @@ K_drawMiniPing (void) } } -static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2]) +static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2], INT32 textflags) { - const INT32 flags = V_SNAPTORIGHT | V_SLIDEIN; - const INT32 textflags = flags | V_6WIDTHSPACE | V_ALLOWLOWERCASE; + INT32 flags = V_SNAPTORIGHT | V_SLIDEIN | V_SPLITSCREEN; const UINT8 anim_duration = 16; const UINT8 anim = (leveltime % (anim_duration * 2)) < anim_duration; - const INT32 x = BASEVIDWIDTH - 60; - const INT32 y = BASEVIDHEIGHT - 70 + (idx * 16); + INT32 x = (BASEVIDWIDTH/2) - 10; + INT32 y = (idx * 16); + + if (r_splitscreen <= 1) + { + x = BASEVIDWIDTH - 60; + if (r_splitscreen == 0) + { + y += BASEVIDHEIGHT - 78; + } + } + + textflags |= (flags | V_6WIDTHSPACE | V_ALLOWLOWERCASE); V_DrawScaledPatch(x, y - 4, flags, kp[anim]); V_DrawRightAlignedThinString(x - 2, y, textflags, label); @@ -4656,14 +4666,49 @@ static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2]) static void K_drawDirectorHUD(void) { + const INT32 p = (splitscreen_partied[consoleplayer] ? splitscreen_party[consoleplayer] : g_localplayers)[R_GetViewNumber()]; + const char *itemtxt = "Join"; + UINT8 offs = 0; + + UINT8 numingame = 0; + UINT8 i; + if (!LUA_HudEnabled(hud_textspectator)) { return; } - K_DrawDirectorButton(0, "Next Player", kp_button_a[0]); - K_DrawDirectorButton(1, "Prev Player", kp_button_x[0]); - K_DrawDirectorButton(2, "Director", kp_button_r); + for (i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && !players[i].spectator) + numingame++; + + if (numingame > 1 && r_splitscreen == 0) // simplifies things a lot + { + K_DrawDirectorButton(1, "Next Player", kp_button_a[0], 0); + K_DrawDirectorButton(2, "Prev Player", kp_button_x[0], 0); + offs = 2; + } + else + + if (p == -1 || !playeringame[p] || players[p].spectator == false) + { + return; + } + + K_DrawDirectorButton(offs + 1, "Director", kp_button_r, + (directorinfo.active ? V_YELLOWMAP : 0)); + + if (players[p].flashing) + itemtxt = ". . ."; + else if (players[p].pflags & PF_WANTSTOJOIN) + itemtxt = "Cancel Join"; + + if (cv_maxplayers.value) + { + itemtxt = va("%s [%d/%d]", itemtxt, numingame, cv_maxplayers.value); + } + + K_DrawDirectorButton(0, itemtxt, kp_button_l, 0); } static void K_drawDistributionDebugger(void) @@ -4984,7 +5029,7 @@ void K_drawKartHUD(void) K_drawMiniPing(); } - if (displayplayers[viewnum] != g_localplayers[viewnum]) + if (displayplayers[viewnum] != g_localplayers[viewnum] && !demo.playback) { K_drawDirectorHUD(); } From 393cf6e7f3a8a59b5171211ae332ec6c0d83b22c Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 1 Mar 2023 07:43:23 -0800 Subject: [PATCH 43/60] Don't let the player use the item button until completely finished respawning --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 5d4c7eb5d..c58ee1d72 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -10099,7 +10099,7 @@ void K_UnsetItemOut(player_t *player) void K_MoveKartPlayer(player_t *player, boolean onground) { ticcmd_t *cmd = &player->cmd; - boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK)); + boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK) && (player->respawn.state == RESPAWNST_NONE)); boolean HOLDING_ITEM = (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT)); boolean NO_HYUDORO = (player->stealingtimer == 0); From 36afe024c79f759caeb8c72dd571734a39f410fe Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 16:06:12 +0000 Subject: [PATCH 44/60] Spurious else --- src/k_hud.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/k_hud.c b/src/k_hud.c index 683040065..8fd7607ec 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4688,7 +4688,6 @@ static void K_drawDirectorHUD(void) K_DrawDirectorButton(2, "Prev Player", kp_button_x[0], 0); offs = 2; } - else if (p == -1 || !playeringame[p] || players[p].spectator == false) { From 8af130f79254b37ed2a689f9c022b80e9ba781bc Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 24 Sep 2022 03:05:30 -0700 Subject: [PATCH 45/60] Let Drop Targets collide with other players immediately after being thrown --- src/k_collide.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_collide.c b/src/k_collide.c index 33834e88f..ab0824eb2 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -474,7 +474,7 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) { mobj_t *draggeddroptarget = (t1->type == MT_DROPTARGET_SHIELD) ? t1->target : NULL; - if ((t1->threshold > 0 && (t2->hitlag > 0 || !draggeddroptarget)) || (t2->threshold > 0 && t1->hitlag > 0)) + if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) From de74808eaacbfd48d60d6db745cb1afa3b2a7f67 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 24 Sep 2022 03:05:52 -0700 Subject: [PATCH 46/60] Let Drop Targets collide with other items immediately after being thrown see efc415e5 --- src/k_collide.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_collide.c b/src/k_collide.c index ab0824eb2..f2fc379ae 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -477,7 +477,7 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (t1->target == t2->target)) && ((t1->threshold > 0 && t2->type == MT_PLAYER) || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) From b2913b9a190ea7817effe472dc16fbf0bf299b47 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 24 Sep 2022 03:46:41 -0700 Subject: [PATCH 47/60] Let Drop Targets hit the thrower immediately after bouncing off a wall --- src/p_mobj.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/p_mobj.c b/src/p_mobj.c index d9920a928..90b31b1bc 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1791,6 +1791,8 @@ void P_XYMovement(mobj_t *mo) { S_StartSound(mo, mo->info->attacksound); mo->health--; + // This prevents an item thrown at a wall from + // phasing through you on its return. mo->threshold = 0; } /*FALLTHRU*/ @@ -1811,6 +1813,12 @@ void P_XYMovement(mobj_t *mo) S_StartSound(mo, sfx_s3k44); // Bubble bounce break; + case MT_DROPTARGET: + // This prevents an item thrown at a wall from + // phasing through you on its return. + mo->threshold = 0; + break; + default: break; } From b2268157fda15b6e948d1777bc78004f0024b90b Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 24 Sep 2022 04:22:30 -0700 Subject: [PATCH 48/60] Fix type check for Land Mines vs Drop Target/Bubble Shield expand Fixes Drop Targets not pushing Land Mines. --- src/p_map.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_map.c b/src/p_map.c index 1f7051c10..72d96885b 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -931,7 +931,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || (tm.thing->player && tm.thing->player->bubbleblowup)) && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG - || thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_SSMINE || thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER && tm.thing->target != thing))) { @@ -967,7 +967,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) else if ((tm.thing->type == MT_DROPTARGET || tm.thing->type == MT_DROPTARGET_SHIELD) && (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG - || thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK + || thing->type == MT_SSMINE || thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP || (thing->type == MT_PLAYER))) { From 13a987a0a22f2bbbeba22769a1d9a05114f7706b Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 16:32:23 +0000 Subject: [PATCH 49/60] Move starting countdown sounds to p_tick.c Fixes an issue where the starting countdown wouldn't play in spectator mode or hitlag --- src/k_kart.c | 12 ------------ src/p_tick.c | 13 +++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 5d4c7eb5d..24e0b9d7b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11036,18 +11036,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { player->pflags &= ~PF_AIRFAILSAFE; } - - // Play the starting countdown sounds - if (player == &players[g_localplayers[0]]) // Don't play louder in splitscreen - { - if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) - S_StartSound(NULL, sfx_s3ka7); - - if (leveltime == starttime-(3*TICRATE)) - S_FadeOutStopMusic(3500); - else if (leveltime == starttime) - S_StartSound(NULL, sfx_s3kad); - } } void K_CheckSpectateStatus(void) diff --git a/src/p_tick.c b/src/p_tick.c index 91be6d183..28de3a6c2 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -659,6 +659,19 @@ void P_Ticker(boolean run) // Plays the music after the starting countdown. else { + if (leveltime == starttime-(3*TICRATE)) + { + S_StartSound(NULL, sfx_s3ka7); // 3, + S_FadeOutStopMusic(3500); + } + else if ((leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) + { + S_StartSound(NULL, sfx_s3ka7); // 2, 1, + } + else if (leveltime == starttime) + { + S_StartSound(NULL, sfx_s3kad); // GO! + } if (leveltime == (starttime + (TICRATE/2))) { S_ChangeMusic(mapmusname, mapmusflags, true); From 7a8a377d930b88b667d493ea8c8e6a56725ef662 Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 24 Sep 2022 05:04:24 -0700 Subject: [PATCH 50/60] Bubble reflect Drop Targets --- src/k_collide.c | 2 +- src/p_map.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_collide.c b/src/k_collide.c index f2fc379ae..ee773dfa3 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -711,7 +711,7 @@ boolean K_BubbleShieldCollide(mobj_t *t1, mobj_t *t2) } else { - if (!t2->threshold) + if (!t2->threshold || t2->type == MT_DROPTARGET) { if (!t2->momx && !t2->momy) { diff --git a/src/p_map.c b/src/p_map.c index 72d96885b..bb1953414 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -917,6 +917,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG || tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK || tm.thing->type == MT_GARDENTOP + || tm.thing->type == MT_DROPTARGET || (tm.thing->type == MT_PLAYER && thing->target != tm.thing))) { // see if it went over / under @@ -933,6 +934,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) || thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG || thing->type == MT_SSMINE || thing->type == MT_LANDMINE || thing->type == MT_SINK || thing->type == MT_GARDENTOP + || thing->type == MT_DROPTARGET || (thing->type == MT_PLAYER && tm.thing->target != thing))) { // see if it went over / under From a2b5844ae8bacdf73eb7c63c8c59dbd6b85e80cc Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 17:03:47 +0000 Subject: [PATCH 51/60] Use S_FadeMusic instead of S_FadeOutStopMusic near starttime Had no deleterious effect when the I_FadeSong hits 0 volume while paused, and seems to be a possible solution to fixing the music-stopping issue..? --- src/p_tick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index 28de3a6c2..939836719 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -662,7 +662,7 @@ void P_Ticker(boolean run) if (leveltime == starttime-(3*TICRATE)) { S_StartSound(NULL, sfx_s3ka7); // 3, - S_FadeOutStopMusic(3500); + S_FadeMusic(0, 3500); //S_FadeOutStopMusic(3500); -- TODO the S_StopMusic callback can halt successor music instead } else if ((leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) { From 5f74d8ca85f287469eec02a38ecf930ac69be008 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 17:15:35 +0000 Subject: [PATCH 52/60] Missing else. I am messing up my elses today --- src/p_tick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index 939836719..d38d48433 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -672,7 +672,7 @@ void P_Ticker(boolean run) { S_StartSound(NULL, sfx_s3kad); // GO! } - if (leveltime == (starttime + (TICRATE/2))) + else if (leveltime == (starttime + (TICRATE/2))) { S_ChangeMusic(mapmusname, mapmusflags, true); S_ShowMusicCredit(); From f3052463111a7d42fb2c65d314cb419164ac8eb9 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 17:21:22 +0000 Subject: [PATCH 53/60] Cleaned up commenting and conditions for start countdown changes --- src/p_tick.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index d38d48433..70c4a14f3 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -647,18 +647,18 @@ void P_Ticker(boolean run) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) P_PlayerAfterThink(&players[i]); - // Bosses have a punchy start, so no position. if (K_CheckBossIntro() == true) { + // Bosses have a punchy start, so no position. if (leveltime == 3) { S_ChangeMusic(mapmusname, mapmusflags, true); S_ShowMusicCredit(); } } - // Plays the music after the starting countdown. - else + else if (leveltime < starttime + TICRATE) { + // Start countdown/music handling if (leveltime == starttime-(3*TICRATE)) { S_StartSound(NULL, sfx_s3ka7); // 3, @@ -674,10 +674,12 @@ void P_Ticker(boolean run) } else if (leveltime == (starttime + (TICRATE/2))) { + // Plays the music after the starting countdown. S_ChangeMusic(mapmusname, mapmusflags, true); S_ShowMusicCredit(); } + // POSITION!! music if (encoremode) { // Encore humming starts immediately. From e4885aab472284b932d899a7056297cf26bfff77 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 1 Mar 2023 10:13:13 -0800 Subject: [PATCH 54/60] Add a short help instruction to "Game data is from the future!" error Game data is from the future! (expected 69, got 420) Rename or delete developringdata.dat (maybe in the Ring Racers folder) and try again. --- src/g_game.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index e46490b07..8be49fc55 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4319,6 +4319,14 @@ void G_LoadGameSettings(void) #define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual #define GD_VERSIONMINOR 1 // Change every format update +static const char *G_GameDataFolder(void) +{ + if (strcmp(srb2home,".")) + return srb2home; + else + return "the Ring Racers folder"; +} + // G_LoadGameData // Loads the main data file, which stores information such as emblems found, etc. void G_LoadGameData(void) @@ -4368,19 +4376,19 @@ void G_LoadGameData(void) versionID = READUINT32(save.p); if (versionID != GD_VERSIONCHECK) { - const char *gdfolder = "the Ring Racers folder"; - if (strcmp(srb2home,".")) - gdfolder = srb2home; + const char *gdfolder = G_GameDataFolder(); P_SaveBufferFree(&save); - I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); + I_Error("Game data is not for Ring Racers v2.0.\nDelete %s (maybe in %s) and try again.", gamedatafilename, gdfolder); } versionMinor = READUINT8(save.p); if (versionMinor > GD_VERSIONMINOR) { + const char *gdfolder = G_GameDataFolder(); + P_SaveBufferFree(&save); - I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor); + I_Error("Game data is from the future! (expected %d, got %d)\nRename or delete %s (maybe in %s) and try again.", GD_VERSIONMINOR, versionMinor, gamedatafilename, gdfolder); } if (versionMinor == 0) { From 4e9d2295c9da30888fea77825c276bac831aecb3 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 19:02:54 +0000 Subject: [PATCH 55/60] Obj_OrbinautJawzMoveHeld: Make loop iteration less fragile MF_NOCLIPTHING is no longer enough to prevent the object from being removed. All calls which can delete the object should now be handled more thoroughly. --- src/objects/orbinaut.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 27e3fe742..6f9ba3788 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -335,23 +335,24 @@ void Obj_OrbinautJawzMoveHeld(player_t *player) { fixed_t finalscale = K_ItemScaleForPlayer(player); fixed_t speed = 0; - mobj_t *cur = NULL; + mobj_t *cur = NULL, *next = player->mo->hnext; player->bananadrag = 0; // Just to make sure - cur = player->mo->hnext; - speed = ((8 - min(4, player->itemamount)) * cur->info->speed) / 7; + if (next == NULL) + return; - while (cur != NULL && P_MobjWasRemoved(cur) == false) + speed = ((8 - min(4, player->itemamount)) * next->info->speed) / 7; + + while ((cur = next) != NULL && P_MobjWasRemoved(cur) == false) { const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius. fixed_t z; + next = cur->hnext; + if (!cur->health) - { - cur = cur->hnext; continue; - } cur->color = player->skincolor; @@ -391,15 +392,17 @@ void Obj_OrbinautJawzMoveHeld(player_t *player) z = player->mo->z + player->mo->height - cur->height; } - cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player + cur->flags |= (MF_NOCLIP|MF_NOCLIPTHING); // temporarily make them noclip other objects so they can't hit anyone while in the player P_MoveOrigin(cur, player->mo->x, player->mo->y, z); cur->momx = FixedMul(FINECOSINE(cur->angle >> ANGLETOFINESHIFT), orbinaut_shield_dist(cur)); cur->momy = FixedMul(FINESINE(cur->angle >> ANGLETOFINESHIFT), orbinaut_shield_dist(cur)); - cur->flags &= ~MF_NOCLIPTHING; + cur->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING); if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true, NULL)) { P_SlideMove(cur, NULL); + if (P_MobjWasRemoved(cur)) + continue; } if (P_IsObjectOnGround(player->mo)) @@ -426,8 +429,6 @@ void Obj_OrbinautJawzMoveHeld(player_t *player) cur->z = z; cur->momx = cur->momy = 0; cur->angle += ANGLE_90; - - cur = cur->hnext; } } From ac086fe09a2288ccdd6e494f537014157d526c76 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 19:03:51 +0000 Subject: [PATCH 56/60] K_PuntMine: Same MF_NOCLIP change regression fix --- src/k_kart.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 813c0f441..60a3ea2e8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5625,7 +5625,7 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter) spd = FixedMul(82 * punter->scale, K_GetKartGameSpeedScalar(gamespeed)); // Avg Speed is 41 in Normal - mine->flags |= MF_NOCLIPTHING; + mine->flags |= (MF_NOCLIP|MF_NOCLIPTHING); P_SetMobjState(mine, S_SSMINE_AIR1); mine->threshold = 10; @@ -5637,7 +5637,7 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter) //K_SetHitLagForObjects(punter, mine, 5); - mine->flags &= ~MF_NOCLIPTHING; + mine->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING); } #define THUNDERRADIUS 320 @@ -6150,11 +6150,11 @@ void K_DropHnextList(player_t *player, boolean keepshields) { fixed_t radius = FixedMul(work->info->radius, dropwork->destscale); radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(radius, radius); // mobj's distance from its Target, or Radius. - dropwork->flags |= MF_NOCLIPTHING; + dropwork->flags |= (MF_NOCLIP|MF_NOCLIPTHING); work->momx = FixedMul(FINECOSINE(work->angle>>ANGLETOFINESHIFT), radius); work->momy = FixedMul(FINESINE(work->angle>>ANGLETOFINESHIFT), radius); P_MoveOrigin(dropwork, player->mo->x + work->momx, player->mo->y + work->momy, player->mo->z); - dropwork->flags &= ~MF_NOCLIPTHING; + dropwork->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING); } dropwork->flags2 |= MF2_AMBUSH; From 06ccdbafc788a42ad3b3e22711a6fbe0ea6bf435 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 1 Mar 2023 23:16:29 +0000 Subject: [PATCH 57/60] P_DeathThink: Don't tick K_KartPlayerHUDUpdate This function is already called regardless of whether the player is dead or alive in p_tick.c --- src/p_user.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index 7826d4071..b3c605159 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2691,8 +2691,6 @@ static void P_DeathThink(player_t *player) else player->karthud[khud_timeovercam] = 0; - K_KartPlayerHUDUpdate(player); - if (player->pflags & PF_NOCONTEST) { playerGone = true; From df5f24e5c7d30f8ed8ad1a8af2413eecad7efcdc Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 1 Mar 2023 18:54:31 -0600 Subject: [PATCH 58/60] hwr2: separate Modulate and Alpha blending in 2d --- src/hwr2/pass_twodee.cpp | 14 +++++++++++++- src/hwr2/twodee.hpp | 3 ++- src/v_video.cpp | 12 ++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/hwr2/pass_twodee.cpp b/src/hwr2/pass_twodee.cpp index da3e47176..e9316502f 100644 --- a/src/hwr2/pass_twodee.cpp +++ b/src/hwr2/pass_twodee.cpp @@ -287,7 +287,7 @@ static PipelineDesc make_pipeline_desc(TwodeePipelineKey key) BlendDesc blend_desc; switch (key.blend) { - case Draw2dBlend::kModulate: + case Draw2dBlend::kAlphaTransparent: blend_desc.source_factor_color = BlendFactor::kSourceAlpha; blend_desc.dest_factor_color = BlendFactor::kOneMinusSourceAlpha; blend_desc.color_function = BlendFunction::kAdd; @@ -295,6 +295,14 @@ static PipelineDesc make_pipeline_desc(TwodeePipelineKey key) blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; blend_desc.alpha_function = BlendFunction::kAdd; break; + case Draw2dBlend::kModulate: + blend_desc.source_factor_color = BlendFactor::kDest; + blend_desc.dest_factor_color = BlendFactor::kZero; + blend_desc.color_function = BlendFunction::kAdd; + blend_desc.source_factor_alpha = BlendFactor::kDestAlpha; + blend_desc.dest_factor_alpha = BlendFactor::kZero; + blend_desc.alpha_function = BlendFunction::kAdd; + break; case Draw2dBlend::kAdditive: blend_desc.source_factor_color = BlendFactor::kSourceAlpha; blend_desc.dest_factor_color = BlendFactor::kOne; @@ -454,21 +462,25 @@ void TwodeePass::prepass(Rhi& rhi) if (data_->pipelines.size() == 0) { + TwodeePipelineKey alpha_transparent_tris = {Draw2dBlend::kAlphaTransparent, false}; TwodeePipelineKey modulate_tris = {Draw2dBlend::kModulate, false}; TwodeePipelineKey additive_tris = {Draw2dBlend::kAdditive, false}; TwodeePipelineKey subtractive_tris = {Draw2dBlend::kSubtractive, false}; TwodeePipelineKey revsubtractive_tris = {Draw2dBlend::kReverseSubtractive, false}; TwodeePipelineKey invertdest_tris = {Draw2dBlend::kInvertDest, false}; + TwodeePipelineKey alpha_transparent_lines = {Draw2dBlend::kAlphaTransparent, true}; TwodeePipelineKey modulate_lines = {Draw2dBlend::kModulate, true}; TwodeePipelineKey additive_lines = {Draw2dBlend::kAdditive, true}; TwodeePipelineKey subtractive_lines = {Draw2dBlend::kSubtractive, true}; TwodeePipelineKey revsubtractive_lines = {Draw2dBlend::kReverseSubtractive, true}; TwodeePipelineKey invertdest_lines = {Draw2dBlend::kInvertDest, true}; + data_->pipelines.insert({alpha_transparent_tris, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_tris))}); data_->pipelines.insert({modulate_tris, rhi.create_pipeline(make_pipeline_desc(modulate_tris))}); data_->pipelines.insert({additive_tris, rhi.create_pipeline(make_pipeline_desc(additive_tris))}); data_->pipelines.insert({subtractive_tris, rhi.create_pipeline(make_pipeline_desc(subtractive_tris))}); data_->pipelines.insert({revsubtractive_tris, rhi.create_pipeline(make_pipeline_desc(revsubtractive_tris))}); data_->pipelines.insert({invertdest_tris, rhi.create_pipeline(make_pipeline_desc(invertdest_tris))}); + data_->pipelines.insert({alpha_transparent_lines, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_lines))}); data_->pipelines.insert({modulate_lines, rhi.create_pipeline(make_pipeline_desc(modulate_lines))}); data_->pipelines.insert({additive_lines, rhi.create_pipeline(make_pipeline_desc(additive_lines))}); data_->pipelines.insert({subtractive_lines, rhi.create_pipeline(make_pipeline_desc(subtractive_lines))}); diff --git a/src/hwr2/twodee.hpp b/src/hwr2/twodee.hpp index 10cc8e6b1..af0f60bc5 100644 --- a/src/hwr2/twodee.hpp +++ b/src/hwr2/twodee.hpp @@ -40,6 +40,7 @@ struct TwodeeVertex enum class Draw2dBlend { + kAlphaTransparent, kModulate, kAdditive, kSubtractive, @@ -80,7 +81,7 @@ struct Draw2dVertices std::size_t begin_index = 0; std::size_t begin_element = 0; std::size_t elements = 0; - Draw2dBlend blend = Draw2dBlend::kModulate; + Draw2dBlend blend = Draw2dBlend::kAlphaTransparent; lumpnum_t flat_lump = UINT32_MAX; // LUMPERROR but not loading w_wad.h from this header bool lines = false; }; diff --git a/src/v_video.cpp b/src/v_video.cpp index b9ea539bb..65bc2b2ef 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -914,7 +914,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca { falpha = (10 - alphalevel) / 10.f; } - hwr2::Draw2dBlend blend = hwr2::Draw2dBlend::kModulate; + hwr2::Draw2dBlend blend = hwr2::Draw2dBlend::kAlphaTransparent; switch (blendmode) { case AST_MODULATE: @@ -934,7 +934,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca blend = hwr2::Draw2dBlend::kSubtractive; break; default: - blend = hwr2::Draw2dBlend::kModulate; + blend = hwr2::Draw2dBlend::kAlphaTransparent; break; } @@ -1187,7 +1187,7 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) float a = 0.5f; // alphalevel is unused in GL?? g_2d.begin_quad() .rect(x, y, w, h) - .blend(hwr2::Draw2dBlend::kModulate) + .blend(hwr2::Draw2dBlend::kAlphaTransparent) .color(r, g, b, a) .done(); } @@ -1356,7 +1356,7 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U g = bc.green / 255.f; b = bc.blue / 255.f; a = softwaretranstohwr[std::clamp(static_cast(strength), 0, 10)] / 255.f; - blendmode = hwr2::Draw2dBlend::kModulate; + blendmode = hwr2::Draw2dBlend::kAlphaTransparent; } g_2d.begin_quad() @@ -1553,7 +1553,7 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength) g = bc.green / 255.f; b = bc.blue / 255.f; a = softwaretranstohwr[std::clamp(static_cast(strength), 0, 10)] / 255.f; - blendmode = hwr2::Draw2dBlend::kModulate; + blendmode = hwr2::Draw2dBlend::kAlphaTransparent; } g_2d.begin_quad() @@ -1629,7 +1629,7 @@ void V_DrawFadeConsBack(INT32 plines) float a = 0.5f; g_2d.begin_quad() .rect(0, 0, vid.width, plines) - .blend(hwr2::Draw2dBlend::kModulate) + .blend(hwr2::Draw2dBlend::kAlphaTransparent) .color(r, g, b, a) .done(); } From 5d54d53a4b589066fcf9dcdb8136d18d8da4fcff Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 1 Mar 2023 19:02:20 -0600 Subject: [PATCH 59/60] Hide movie_showfps option when webm is disabled --- src/menus/options-data-screenshots.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/menus/options-data-screenshots.c b/src/menus/options-data-screenshots.c index 809eb5e1b..0cb1d0762 100644 --- a/src/menus/options-data-screenshots.c +++ b/src/menus/options-data-screenshots.c @@ -3,7 +3,9 @@ #include "../k_menu.h" #include "../m_misc.h" // screenshot cvars +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "../m_avrecorder.h" +#endif menuitem_t OPTIONS_DataScreenshot[] = { @@ -26,15 +28,19 @@ menuitem_t OPTIONS_DataScreenshot[] = {IT_STRING | IT_CVAR, "Recording Format", "What file format will movies will be recorded in?", NULL, {.cvar = &cv_moviemode}, 0, 0}, +#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES {IT_STRING | IT_CVAR, "Real-Time Data", "If enabled, shows fps, duration and filesize of recording in real-time.", NULL, {.cvar = &cv_movie_showfps}, 0, 0}, +#else + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, +#endif {IT_STRING | IT_CVAR, "Storage Location", "Sets where to store movies.", NULL, {.cvar = &cv_movie_option}, 0, 0}, {IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save videos in.", NULL, {.cvar = &cv_movie_folder}, 24, 0}, - }; menu_t OPTIONS_DataScreenshotDef = { From 746171a8ad8144c3170ae2ee96ecf28a18b4d8e6 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 2 Mar 2023 02:47:29 -0700 Subject: [PATCH 60/60] Fix powerup music inconsistencies --- src/k_kart.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 813c0f441..ff5b0b64d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5930,9 +5930,7 @@ void K_DoInvincibility(player_t *player, tic_t time) P_SetScale(overlay, player->mo->scale); } - player->invincibilitytimer += time; - - if (P_IsLocalPlayer(player) == true) + if (P_IsLocalPlayer(player) == true && player->invincibilitytimer == 0) { S_ChangeMusicSpecial("kinvnc"); } @@ -5941,6 +5939,8 @@ void K_DoInvincibility(player_t *player, tic_t time) S_StartSound(player->mo, sfx_alarmi); } + player->invincibilitytimer += time; + P_RestoreMusic(player); } @@ -10540,11 +10540,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) S_StartSound(player->mo, sfx_alarmg); } - P_RestoreMusic(player); - player->growshrinktimer = max(0, player->growshrinktimer); player->growshrinktimer += ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE; + P_RestoreMusic(player); + S_StartSound(player->mo, sfx_kc5a); player->itemamount--;