Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into conditions-cascading

This commit is contained in:
toaster 2023-03-02 14:38:56 +00:00
commit 92285b90fa
110 changed files with 6773 additions and 1648 deletions

View file

@ -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)
@ -132,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})

View file

@ -32,13 +32,13 @@ 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
m_easing.c
m_fixed.c
m_misc.c
m_memcpy.c
m_misc.cpp
m_perfstats.c
m_random.c
m_queue.c
@ -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
@ -135,6 +136,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.
@ -225,14 +230,18 @@ 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)
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)
@ -550,7 +559,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.

View file

@ -559,10 +559,7 @@ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo
(void)argC;
if (ACS_ActivatorIsLocal(thread) == true)
{
HU_SetCEchoDuration(5);
HU_DoCEcho(thread->printBuf.data());
}
HU_DoTitlecardCEcho(thread->printBuf.data());
thread->printBuf.drop();
return false;
@ -931,13 +928,11 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM
(void)argV;
(void)argC;
HU_SetCEchoDuration(5);
HU_DoCEcho(thread->printBuf.data());
HU_DoTitlecardCEcho(thread->printBuf.data());
thread->printBuf.drop();
return false;
}
/*--------------------------------------------------
bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)

View file

@ -103,7 +103,7 @@ Environment::Environment()
addCodeDataACS0(120, {"", 0, addCallFunc(CallFunc_PlayerRings)});
addCodeDataACS0(122, {"", 0, addCallFunc(CallFunc_PlayerScore)});
// 136 to 137: Implemented by ACSVM
// 157: Implemented by ACSVM

View file

@ -162,6 +162,17 @@ struct Overload : Ts... {
template <typename... Ts>
Overload(Ts...) -> Overload<Ts...>;
inline void hash_combine(std::size_t& seed)
{}
template <class T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
hash_combine(seed, std::forward<Rest>(rest)...);
}
} // namespace srb2
#endif // __SRB2_CXXUTIL_HPP__

View file

@ -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)
M_SaveFrame();
#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]);
}

View file

@ -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
@ -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;
}
}
@ -800,6 +803,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
@ -865,11 +871,13 @@ void D_SRB2Loop(void)
D_Display();
}
#ifdef HWRENDER
// Only take screenshots after drawing.
if (moviemode)
M_SaveFrame();
if (takescreenshot)
M_DoScreenShot();
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
if (rendermode == render_opengl && takescreenshot)
M_DoLegacyGLScreenShot();
#endif
// consoleplayer -> displayplayers (hear sounds from viewpoint)
S_UpdateSounds(); // move positional sounds
@ -1489,6 +1497,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,

View file

@ -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"
@ -545,8 +548,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);
@ -904,7 +905,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);
@ -1053,8 +1058,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);
@ -4096,7 +4099,7 @@ void Schedule_Insert(scheduleTask_t *addTask)
{
schedule_size *= 2;
}
schedule = Z_ReallocAlign(
(void*) schedule,
sizeof(scheduleTask_t*) * schedule_size,

View file

@ -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;

View file

@ -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

View file

@ -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[] = {

View file

@ -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. =)
@ -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
M_SaveFrame();
#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--;
}
@ -2435,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();
}
@ -2455,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);

View file

@ -140,6 +140,12 @@ extern UINT16 curtttics;
//
extern boolean WipeInAction;
extern UINT8 g_wipemode;
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;
@ -150,11 +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 wipemode);
/// @brief true if the wipetype is to-white
boolean F_WipeIsToWhite(UINT8 wipemode);
/// @brief true if the wipetype is to-invert
boolean F_WipeIsToInvert(UINT8 wipemode);
/// @brief true if the wipetype is modulated from the previous frame
boolean F_WipeIsCrossfade(UINT8 wipemode);
enum
{

View file

@ -85,11 +85,149 @@ 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
//--------------------------------------------------------------------------
boolean WipeInAction = false;
UINT8 g_wipemode = 0;
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;
@ -189,152 +327,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)<<FF_TRANSSHIFT);
// DRAWING LOOP
while (draw_linestogo--)
{
w = w_base + relativepos;
s = s_base + relativepos;
e = e_base + relativepos;
draw_rowstogo = draw_rowend - draw_rowstart;
if (fadecolormap)
{
if (reverse)
s = e;
while (draw_rowstogo--)
*w++ = fadecolormap[ ( m << 8 ) + *s++ ];
}
else while (draw_rowstogo--)
{
/*if (fadecolormap != NULL)
{
if (reverse)
*w++ = fadecolormap[ ( m << 8 ) + *e++ ];
else
*w++ = fadecolormap[ ( m << 8 ) + *s++ ];
}
else*/
*w++ = transtbl[ ( *e++ << 8 ) + *s++ ];
}
relativepos += vid.width;
}
// END DRAWING LOOP
}
if (++maskx >= fademask->width)
++masky, maskx = 0;
} while (++mask < maskend);
free(scrxpos);
free(scrypos);
}
}
#endif
/** Save the "before" screen of a wipe.
@ -351,6 +343,7 @@ void F_WipeStartScreen(void)
#endif
wipe_scr_start = screens[3];
I_ReadScreen(wipe_scr_start);
I_FinishUpdateWipeStartScreen();
#endif
}
@ -369,54 +362,10 @@ void F_WipeEndScreen(void)
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
}
/** 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)
@ -432,9 +381,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;
@ -467,6 +417,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,14 +445,19 @@ 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_wipemode = wipemode;
g_wipetype = wipetype;
g_wipeframe = wipeframe - 1;
g_wipereverse = reverse;
if (encorewiggle)
{
#ifdef HWRENDER
if (rendermode != render_opengl)
#endif
F_DoEncoreWiggle(wipeframe);
g_wipeencorewiggle = wipeframe - 1;
}
else
{
g_wipeencorewiggle = 0;
}
}
@ -519,15 +475,24 @@ 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 (moviemode)
M_SaveFrame();
if (rendermode != render_none)
{
// Skip subsequent renders until the end of the wipe to preserve the current frame.
g_wipeskiprender = true;
}
#ifdef HWRENDER
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
#endif
NetKeepAlive(); // Update the network so we don't cause timeouts
}
WipeInAction = false;
g_wipeskiprender = false;
if (fcolor)
{
@ -585,3 +550,24 @@ boolean F_WipeExists(UINT8 wipetype)
return !(lumpnum == LUMPERROR);
#endif
}
boolean F_WipeIsToBlack(UINT8 wipemode)
{
return g_wipedef_toblack[wipemode];
}
boolean F_WipeIsToWhite(UINT8 wipemode)
{
return g_wipedef_towhite[wipemode];
}
boolean F_WipeIsToInvert(UINT8 wipemode)
{
return g_wipedef_toinvert[wipemode];
}
boolean F_WipeIsCrossfade(UINT8 wipemode)
{
return g_wipedef_crossfade[wipemode];
}

View file

@ -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)

View file

@ -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"
@ -1164,6 +1165,37 @@ 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 (player->spectator == 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;
}
if (K_PlayerUsesBotMovement(player))
{
// Bot ticcmd is generated by K_BuildBotTiccmd
@ -1352,16 +1384,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)
@ -1556,10 +1578,10 @@ void G_PreLevelTitleCard(void)
I_FinishUpdate(); // page flip or blit buffer
NetKeepAlive(); // Prevent timeouts
if (moviemode)
M_SaveFrame();
if (takescreenshot) // Only take screenshots after drawing.
M_DoScreenShot();
#ifdef HWRENDER
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
#endif
while (!((nowtime = I_GetTime()) - lasttime))
{
@ -1695,44 +1717,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]
@ -2026,6 +2012,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();
}
//
@ -3116,6 +3106,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
}
@ -4095,6 +4086,7 @@ void G_AfterIntermission(void)
gamecomplete = 1;
HU_ClearCEcho();
HU_ClearTitlecardCEcho();
if (demo.playback)
{
@ -4327,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)
@ -4362,7 +4362,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)
@ -4376,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)
{

View file

@ -161,6 +161,11 @@ static tic_t cechotimer = 0;
static tic_t cechoduration = 5*TICRATE;
static INT32 cechoflags = 0;
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
static tic_t resynch_ticker = 0;
static huddrawlist_h luahuddrawlist_scores;
@ -1033,6 +1038,13 @@ void HU_Ticker(void)
if (cechotimer)
cechotimer--;
if (tcechotimer)
{
tcechotimer++;
if (tcechotimer > tcechoduration)
tcechotimer = 0;
}
if (gamestate != GS_LEVEL)
{
@ -1996,6 +2008,66 @@ static void HU_DrawCEcho(void)
}
}
static void HU_DrawTitlecardCEcho(void)
{
if (tcechotimer)
{
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++;
}
}
}
//
// demo info stuff
//
@ -2137,6 +2209,9 @@ drawontop:
if (cechotimer)
HU_DrawCEcho();
if (tcechotimer)
HU_DrawTitlecardCEcho();
}
//======================================================================
@ -2590,3 +2665,22 @@ 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));
strncat(tcechotext, "\\", sizeof(tcechotext) - strlen(tcechotext) - 1);
tcechotext[sizeof(tcechotext) - 1] = '\0';
tcechotimer = 1;
tcechoduration = TICRATE*6 + strlen(tcechotext);
}

View file

@ -154,6 +154,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;

View file

@ -1,8 +1,22 @@
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_screenshot.cpp
pass_screenshot.hpp
pass_software.cpp
pass_software.hpp
pass_twodee.cpp
pass_twodee.hpp
pass.cpp
pass.hpp
twodee.cpp
twodee.hpp
)

View file

@ -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;

View file

@ -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.

209
src/hwr2/pass_blit_rect.cpp Normal file
View file

@ -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 <optional>
#include <tcb/span.hpp>
#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<MainPaletteManager>& 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<TransferContext> 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<float>(texture_width_) / static_cast<float>(texture_height_);
output_aspect = static_cast<float>(output_width_) / static_cast<float>(output_height_);
}
bool taller = aspect > output_aspect;
std::array<rhi::UniformVariant, 1> g1_uniforms = {{
// Projection
std::array<std::array<float, 4>, 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<rhi::UniformVariant, 2> g2_uniforms = {
{// ModelView
std::array<std::array<float, 4>, 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<std::array<float, 3>, 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<rhi::VertexAttributeBufferBinding, 1> vbs = {{{0, quad_vbo_}}};
if (palette_mgr_)
{
std::array<rhi::TextureBinding, 2> tbs = {
{{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, palette_mgr_->palette()}}};
binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs});
}
else
{
std::array<rhi::TextureBinding, 1> 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<GraphicsContext> 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)
{
}

View file

@ -0,0 +1,93 @@
// 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 <array>
#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<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Texture> texture_;
uint32_t texture_width_ = 0;
uint32_t texture_height_ = 0;
rhi::Handle<rhi::Texture> output_;
uint32_t output_width_ = 0;
uint32_t output_height_ = 0;
bool output_correct_aspect_ = false;
bool output_clear_ = false;
bool output_flip_ = false;
rhi::Handle<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Buffer> quad_vbo_;
rhi::Handle<rhi::Buffer> quad_ibo_;
std::array<rhi::Handle<rhi::UniformSet>, 2> uniform_sets_;
rhi::Handle<rhi::BindingSet> 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<MainPaletteManager> palette_mgr_;
public:
BlitRectPass();
BlitRectPass(bool output_clear);
BlitRectPass(const std::shared_ptr<MainPaletteManager>& palette_mgr, bool output_clear);
virtual ~BlitRectPass();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::Texture> 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<rhi::Texture> 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;
}
void clear_output(bool clear) noexcept { output_clear_ = clear; }
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__

View file

@ -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 <imgui.h>
@ -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<Buffer> vbo = rhi.create_buffer(
{
static_cast<uint32_t>(list->VtxBuffer.size_in_bytes()),
BufferType::kVertexBuffer,
BufferUsage::kImmutable
}
{static_cast<uint32_t>(list->VtxBuffer.size_in_bytes()), BufferType::kVertexBuffer, BufferUsage::kImmutable}
);
Handle<Buffer> ibo = rhi.create_buffer(
{
static_cast<uint32_t>(list->IdxBuffer.size_in_bytes()),
BufferType::kIndexBuffer,
BufferUsage::kImmutable
}
{static_cast<uint32_t>(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<int32_t>(clip_min.x),
static_cast<int32_t>((data->DisplaySize.y * data->FramebufferScale.y) - clip_max.y),
static_cast<uint32_t>(clip_max.x - clip_min.x),
static_cast<uint32_t>(clip_max.y - clip_min.y)
};
static_cast<uint32_t>(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<TransferContext> ctx)
rhi.update_buffer_contents(ctx, ibo, 0, tcb::as_bytes(index_span));
// Uniform sets
std::array<UniformVariant, 1> g1_uniforms =
{{
std::array<UniformVariant, 1> g1_uniforms = {{
// Projection
std::array<std::array<float, 4>, 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<UniformVariant, 2> g2_uniforms =
{{
// ModelView
std::array<std::array<float, 4>, 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<std::array<float, 3>, 3>
{{
{1.f, 0.f, 0.f},
{0.f, 1.f, 0.f},
{0.f, 0.f, 1.f}
}}
std::array<std::array<float, 4>, 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<UniformVariant, 2> g2_uniforms = {
{// ModelView
std::array<std::array<float, 4>, 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<std::array<float, 3>, 3> {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}}}};
Handle<UniformSet> us_1 = rhi.create_uniform_set(ctx, {g1_uniforms});
Handle<UniformSet> us_2 = rhi.create_uniform_set(ctx, {g2_uniforms});

View file

@ -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<DrawList> draw_lists_;
public:
ImguiPass();
virtual ~ImguiPass();
virtual void prepass(rhi::Rhi& rhi) override;

191
src/hwr2/pass_manager.cpp Normal file
View file

@ -0,0 +1,191 @@
// 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<void(PassManager&, rhi::Rhi&)> prepass_func_;
std::function<void(PassManager&, rhi::Rhi&)> postpass_func_;
public:
LambdaPass(PassManager* mgr, std::function<void(PassManager&, rhi::Rhi&)> prepass_func);
LambdaPass(
PassManager* mgr,
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
std::function<void(PassManager&, rhi::Rhi&)> postpass_func
);
virtual ~LambdaPass();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
};
} // namespace
LambdaPass::LambdaPass(PassManager* mgr, std::function<void(PassManager&, rhi::Rhi&)> prepass_func)
: mgr_(mgr), prepass_func_(prepass_func)
{
}
LambdaPass::LambdaPass(
PassManager* mgr,
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
std::function<void(PassManager&, rhi::Rhi&)> 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<TransferContext>)
{
}
void LambdaPass::graphics(Rhi&, Handle<GraphicsContext>)
{
}
void LambdaPass::postpass(Rhi& rhi)
{
if (postpass_func_)
{
(postpass_func_)(*mgr_, 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> 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<void(PassManager&, Rhi&)> prepass_func)
{
insert(std::forward<const std::string>(name), std::make_shared<LambdaPass>(LambdaPass {this, prepass_func}));
}
void PassManager::insert(
const std::string& name,
std::function<void(PassManager&, Rhi&)> prepass_func,
std::function<void(PassManager&, Rhi&)> postpass_func
)
{
insert(
std::forward<const std::string>(name),
std::make_shared<LambdaPass>(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<Pass> PassManager::for_name(const std::string& name)
{
auto itr = pass_by_name_.find(name);
if (itr == pass_by_name_.end())
{
return std::weak_ptr<Pass>();
}
return passes_[itr->second].pass;
}
void PassManager::prepass(Rhi& rhi)
{
for (auto& pass : passes_)
{
if (pass.enabled)
{
pass.pass->prepass(rhi);
}
}
}
void PassManager::transfer(Rhi& rhi, Handle<TransferContext> ctx)
{
for (auto& pass : passes_)
{
if (pass.enabled)
{
pass.pass->transfer(rhi, ctx);
}
}
}
void PassManager::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
{
for (auto& pass : passes_)
{
if (pass.enabled)
{
pass.pass->graphics(rhi, ctx);
}
}
}
void PassManager::postpass(Rhi& rhi)
{
for (auto& pass : passes_)
{
if (pass.enabled)
{
pass.pass->postpass(rhi);
}
}
}
void PassManager::render(Rhi& rhi)
{
if (passes_.empty())
{
return;
}
prepass(rhi);
Handle<TransferContext> tc = rhi.begin_transfer();
transfer(rhi, tc);
rhi.end_transfer(tc);
Handle<GraphicsContext> gc = rhi.begin_graphics();
graphics(rhi, gc);
rhi.end_graphics(gc);
postpass(rhi);
}

65
src/hwr2/pass_manager.hpp Normal file
View file

@ -0,0 +1,65 @@
// 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 <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "../rhi/rhi.hpp"
#include "pass.hpp"
namespace srb2::hwr2
{
class PassManager final : public Pass
{
struct PassManagerEntry
{
std::string name;
std::shared_ptr<Pass> pass;
bool enabled;
};
std::unordered_map<std::string, std::size_t> pass_by_name_;
std::vector<PassManagerEntry> passes_;
public:
PassManager();
PassManager(const PassManager&);
PassManager(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<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void insert(const std::string& name, std::shared_ptr<Pass> pass);
void insert(const std::string& name, std::function<void(PassManager&, rhi::Rhi&)> prepass_func);
void insert(
const std::string& name,
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
std::function<void(PassManager&, rhi::Rhi&)> postpass_func
);
std::weak_ptr<Pass> for_name(const std::string& name);
void set_pass_enabled(const std::string& name, bool enabled);
void render(rhi::Rhi& rhi);
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_MANAGER_HPP__

View file

@ -0,0 +1,243 @@
// 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 <string>
#include <fmt/format.h>
#include <tcb/span.hpp>
#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, UniformName::kWipeColorizeMode, UniformName::kWipeEncoreSwizzle}}}},
{{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}},
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()
{
}
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_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_mode))
{
wipe_color_mode_ = 2;
}
else if (F_WipeIsToWhite(wipe_mode))
{
wipe_color_mode_ = 3;
}
else if (F_WipeIsToInvert(wipe_mode))
{
wipe_color_mode_ = 1;
}
else if (F_WipeIsCrossfade(wipe_mode))
{
wipe_color_mode_ = 0;
}
wipe_swizzle_ = g_wipeencorewiggle;
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<TransferContext> ctx)
{
if (wipe_tex_ == kNullHandle)
{
return;
}
if (start_ == kNullHandle || end_ == 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<const std::byte> 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[] = {
{std::array<std::array<float, 4>, 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<int32_t>(wipe_color_mode_)},
{static_cast<int32_t>(wipe_swizzle_)}};
us_ = rhi.create_uniform_set(ctx, {tcb::span(uniforms)});
VertexAttributeBufferBinding vbos[] = {{0, vbo_}};
TextureBinding tx[] = {
{SamplerName::kSampler0, start_},
{SamplerName::kSampler1, end_},
{SamplerName::kSampler2, wipe_tex_}};
bs_ = rhi.create_binding_set(ctx, pipeline_, {vbos, tx});
}
void PostprocessWipePass::graphics(Rhi& rhi, Handle<GraphicsContext> 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();
}

View file

@ -0,0 +1,70 @@
// 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 <vector>
namespace srb2::hwr2
{
class PostprocessWipePass final : public Pass
{
// Internal RHI resources
rhi::Handle<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Buffer> vbo_;
bool upload_vbo_ = false;
rhi::Handle<rhi::Buffer> ibo_;
bool upload_ibo_ = false;
rhi::Handle<rhi::UniformSet> us_;
rhi::Handle<rhi::BindingSet> bs_;
rhi::Handle<rhi::Texture> wipe_tex_;
int wipe_color_mode_ = 0;
int wipe_swizzle_ = 0;
// Pass parameters
rhi::Handle<rhi::Texture> start_;
rhi::Handle<rhi::Texture> end_;
rhi::Handle<rhi::Texture> target_;
uint32_t target_w_ = 0;
uint32_t target_h_ = 0;
// Mask lump loading
std::vector<uint8_t> 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<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void set_start(rhi::Handle<rhi::Texture> start) noexcept { start_ = start; }
void set_end(rhi::Handle<rhi::Texture> end) noexcept { end_ = end; }
void set_target(rhi::Handle<rhi::Texture> 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__

View file

@ -0,0 +1,285 @@
// 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 <algorithm>
#include <cmath>
#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_color_ != kNullHandle)
{
rhi.destroy_texture(main_color_);
main_color_ = 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;
}
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;
// Recreate the framebuffer textures
if (main_color_ == kNullHandle)
{
main_color_ = 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});
}
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<TransferContext> ctx)
{
}
void FramebufferManager::graphics(Rhi& rhi, Handle<GraphicsContext> 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<TransferContext> ctx)
{
std::array<byteColor_t, 256> 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<GraphicsContext> ctx)
{
}
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<TransferContext> ctx)
{
if (!init_)
{
uint8_t black[4] = {0, 0, 0, 255};
tcb::span<const std::byte> black_bytes = tcb::as_bytes(tcb::span(black, 4));
uint8_t white[4] = {255, 255, 255, 255};
tcb::span<const std::byte> white_bytes = tcb::as_bytes(tcb::span(white, 4));
uint8_t transparent[4] = {0, 0, 0, 0};
tcb::span<const std::byte> 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<GraphicsContext> ctx)
{
}
void CommonResourcesManager::postpass(Rhi& rhi)
{
init_ = true;
}
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<TransferContext> ctx)
{
std::vector<std::array<uint8_t, 2>> flat_data;
for (auto flat_lump : to_upload_)
{
flat_data.clear();
Handle<Texture> 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<const uint8_t*>(W_CacheLumpNum(flat_lump, PU_PATCH));
SRB2_ASSERT(flat_memory != nullptr);
tcb::span<const uint8_t> 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<uint8_t>(0) : static_cast<uint8_t>(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<const std::byte> 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<GraphicsContext> ctx)
{
}
void FlatTextureManager::postpass(Rhi& rhi)
{
}
Handle<Texture> 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<Texture> 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<Texture> 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;
}

View file

@ -0,0 +1,150 @@
// 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 <array>
#include <cstddef>
#include <unordered_map>
#include <vector>
#include "pass.hpp"
namespace srb2::hwr2
{
class FramebufferManager final : public Pass
{
rhi::Handle<rhi::Texture> main_color_;
rhi::Handle<rhi::Renderbuffer> main_depth_;
std::array<rhi::Handle<rhi::Texture>, 2> post_colors_;
rhi::Handle<rhi::Texture> wipe_start_color_;
rhi::Handle<rhi::Texture> wipe_end_color_;
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<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
/// @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<rhi::Texture> main_color() const noexcept { return main_color_; }
rhi::Handle<rhi::Renderbuffer> main_depth() const noexcept { return main_depth_; }
rhi::Handle<rhi::Texture> current_post_color() const noexcept { return post_colors_[post_index_]; }
rhi::Handle<rhi::Texture> previous_post_color() const noexcept
{
if (first_postprocess_)
{
return main_color();
}
return post_colors_[1 - post_index_];
};
rhi::Handle<rhi::Texture> wipe_start_color() const noexcept { return wipe_start_color_; }
rhi::Handle<rhi::Texture> 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_; }
};
class MainPaletteManager final : public Pass
{
rhi::Handle<rhi::Texture> palette_;
public:
MainPaletteManager();
virtual ~MainPaletteManager();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
rhi::Handle<rhi::Texture> palette() const noexcept { return palette_; }
};
class CommonResourcesManager final : public Pass
{
bool init_ = false;
rhi::Handle<rhi::Texture> black_;
rhi::Handle<rhi::Texture> white_;
rhi::Handle<rhi::Texture> transparent_;
public:
CommonResourcesManager();
virtual ~CommonResourcesManager();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
rhi::Handle<rhi::Texture> black() const noexcept { return black_; }
rhi::Handle<rhi::Texture> white() const noexcept { return white_; }
rhi::Handle<rhi::Texture> transparent() const noexcept { return transparent_; }
};
/*
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<lumpnum_t, rhi::Handle<rhi::Texture>> flats_;
std::vector<lumpnum_t> to_upload_;
std::vector<rhi::Handle<rhi::Texture>> disposed_textures_;
public:
FlatTextureManager();
virtual ~FlatTextureManager();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::Texture> find_or_create_indexed(rhi::Rhi& rhi, lumpnum_t flat_lump);
rhi::Handle<rhi::Texture> find_indexed(lumpnum_t flat_lump) const;
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__

View file

@ -0,0 +1,77 @@
// 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 <tcb/span.hpp>
#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 || moviemode != MM_OFF;
}
void ScreenshotPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
{
}
void ScreenshotPass::graphics(Rhi& rhi, Handle<GraphicsContext> 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;
}
if (takescreenshot)
{
M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_)));
}
if (moviemode != MM_OFF)
{
M_SaveFrame(width_, height_, tcb::as_bytes(tcb::span(pixel_data_)));
}
doing_screenshot_ = false;
}

View file

@ -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 <cstddef>
#include <vector>
#include "pass.hpp"
namespace srb2::hwr2
{
class ScreenshotPass : public Pass
{
bool doing_screenshot_ = false;
rhi::Handle<rhi::Texture> source_;
rhi::Handle<rhi::RenderPass> render_pass_;
std::vector<uint8_t> 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<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void set_source(rhi::Handle<rhi::Texture> source, uint32_t width, uint32_t height)
{
source_ = source;
width_ = width;
height_ = height;
}
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_SCREENSHOT_HPP__

View file

@ -1,94 +1,34 @@
// 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 <optional>
#include "../i_video.h"
#include "../v_video.h"
#include <tcb/span.hpp>
#include "../cxxutil.hpp"
#include "../d_netcmd.h"
#ifdef HAVE_DISCORDRPC
#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"
#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 +40,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 +49,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();
@ -125,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)
@ -142,149 +79,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<uint32_t>(vid.width);
uint32_t vid_height = static_cast<uint32_t>(vid.height);
if (screen_tex_ && (screen_tex_width_ < vid_width || screen_tex_height_ < vid_height))
// Prepare RHI resources
if (screen_texture_ && (static_cast<int32_t>(width_) != vid.width || static_cast<int32_t>(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<TransferContext> ctx)
void SoftwarePass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
{
rhi::Rect screen_rect = {
0,
0,
static_cast<uint32_t>(vid.width),
static_cast<uint32_t>(vid.height)
};
tcb::span<uint8_t> screen_span = tcb::span(vid.buffer, static_cast<size_t>(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<TransferContext> 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<byteColor_t, 256> palette_32;
for (size_t i = 0; i < 256; i++)
// Upload screen
tcb::span<const std::byte> 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<TransferContext> 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<float>(vid.width) / static_cast<float>(vid.height);
float real_aspect = static_cast<float>(vid.realwidth) / static_cast<float>(vid.realheight);
bool taller = aspect > real_aspect;
std::array<rhi::UniformVariant, 1> g1_uniforms = {{
// Projection
std::array<std::array<float, 4>, 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<rhi::UniformVariant, 2> g2_uniforms =
{{
// ModelView
std::array<std::array<float, 4>, 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<std::array<float, 3>, 3>
{{
{vid.width / static_cast<float>(screen_tex_width_), 0.f, 0.f},
{0.f, vid.height / static_cast<float>(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<rhi::VertexAttributeBufferBinding, 1> vbs = {{{0, quad_vbo_}}};
std::array<rhi::TextureBinding, 2> 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<GraphicsContext> ctx)
void SoftwarePass::graphics(Rhi& rhi, Handle<GraphicsContext> 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
}

View file

@ -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 <array>
#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP_
#define __SRB2_HWR2_PASS_SOFTWARE_HPP_
#include <cstddef>
#include <vector>
#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<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Texture> screen_tex_;
rhi::Handle<rhi::Texture> palette_tex_;
rhi::Handle<rhi::Buffer> quad_vbo_;
rhi::Handle<rhi::Buffer> quad_ibo_;
std::array<rhi::Handle<rhi::UniformSet>, 2> uniform_sets_;
rhi::Handle<rhi::BindingSet> binding_set_;
rhi::Handle<rhi::Texture> 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<rhi::TransferContext> ctx);
void upload_palette(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> 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<uint8_t> copy_buffer_;
public:
virtual ~SoftwareBlitPass();
SoftwarePass();
virtual ~SoftwarePass();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
rhi::Handle<rhi::Texture> screen_texture() const noexcept { return screen_texture_; }
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP_

945
src/hwr2/pass_twodee.cpp Normal file
View file

@ -0,0 +1,945 @@
// 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 <unordered_set>
#include <stb_rect_pack.h>
#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<Texture> tex;
uint32_t tex_width;
uint32_t tex_height;
std::unordered_map<const patch_t*, AtlasEntry> entries;
std::unique_ptr<stbrp_context> rp_ctx {nullptr};
std::unique_ptr<stbrp_node[]> rp_nodes {nullptr};
Atlas& operator=(Atlas&&) = default;
};
} // namespace
struct srb2::hwr2::TwodeePassData
{
Handle<Texture> default_tex;
Handle<Texture> default_colormap_tex;
std::vector<Atlas> patch_atlases;
std::unordered_map<const patch_t*, size_t> patch_lookup;
std::vector<const patch_t*> patches_to_upload;
std::unordered_map<const uint8_t*, Handle<Texture>> colormaps;
std::vector<const uint8_t*> colormaps_to_upload;
std::unordered_map<TwodeePipelineKey, Handle<Pipeline>> pipelines;
bool upload_default_tex = false;
};
std::shared_ptr<TwodeePassData> srb2::hwr2::make_twodee_pass_data()
{
return std::make_shared<TwodeePassData>();
}
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<stbrp_context>();
new_atlas.rp_nodes = std::make_unique<stbrp_node[]>(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<const patch_t*> patches)
{
// Prepare stbrp rects for patches to be loaded.
std::vector<stbrp_rect> 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<uint32_t>(rect.x);
entry.y = static_cast<uint32_t>(rect.y);
entry.w = static_cast<uint32_t>(rect.w);
entry.h = static_cast<uint32_t>(rect.h);
entry.trim_x = static_cast<uint32_t>(trimmed_rect.x);
entry.trim_y = static_cast<uint32_t>(trimmed_rect.y);
entry.orig_w = static_cast<uint32_t>(patch->width);
entry.orig_h = static_cast<uint32_t>(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<const column_t*>(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<int32_t>(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<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
}
}
maxx += 1;
maxx = std::max(minx, maxx);
maxy = std::max(miny, maxy);
return {minx, miny, static_cast<uint32_t>(maxx - minx), static_cast<uint32_t>(maxy - miny)};
}
static void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector<uint8_t>& 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<int32_t>(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<const column_t*>(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<const uint8_t*>(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<int32_t>(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<const column_t*>(reinterpret_cast<const uint8_t*>(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::kAlphaTransparent:
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::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;
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<float>(entry.x) / atlas.tex_width;
const float atlas_umax = static_cast<float>(entry.x + entry.w) / atlas.tex_width;
const float atlas_vmin = static_cast<float>(entry.y) / atlas.tex_height;
const float atlas_vmax = static_cast<float>(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<float>(entry.trim_x) / entry.orig_w;
const float trim_umax = static_cast<float>(entry.trim_x + entry.w) / entry.orig_w;
const float trim_vmin = static_cast<float>(entry.trim_y) / entry.orig_h;
const float trim_vmax = static_cast<float>(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 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))});
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_->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<const patch_t*> found_patches;
std::unordered_set<const uint8_t*> 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<const patch_t*> patch_cache_hits;
std::unordered_set<const patch_t*> 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<Texture> 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<const patch_t*> 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<Buffer> vbo;
uint32_t vertex_data_size = tcb::as_bytes(tcb::span(list.vertices)).size();
uint32_t needed_vbo_size = std::max(
kVboInitSize,
((static_cast<uint32_t>(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<Buffer> ibo;
uint32_t index_data_size = tcb::as_bytes(tcb::span(list.indices)).size();
uint32_t needed_ibo_size = std::max(
kIboInitSize,
((static_cast<uint32_t>(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<TransferContext> ctx)
{
if (!ctx_ || !data_)
{
return;
}
if (data_->upload_default_tex)
{
std::array<uint8_t, 4> 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<uint8_t, 256> 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;
}
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<uint8_t> 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<int32_t>(entry.x), static_cast<int32_t>(entry.y), entry.w, entry.h},
PixelFormat::kRG8,
tcb::as_bytes(tcb::span(patch_data))
);
}
data_->patches_to_upload.clear();
Handle<Texture> 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++)
{
auto& merged_list = cmd_lists_[i];
auto& orig_list = *ctx_list_itr;
tcb::span<const std::byte> vertex_data = tcb::as_bytes(tcb::span(orig_list.vertices));
tcb::span<const std::byte> 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, palette_tex};
},
[&](const MergedTwodeeCommandFlatTexture& tex)
{
Handle<Texture> th = flat_manager_->find_indexed(tex.lump);
SRB2_ASSERT(th != kNullHandle);
tx[0] = {SamplerName::kSampler0, th};
tx[1] = {SamplerName::kSampler1, palette_tex};
}};
if (mcmd.texture)
{
std::visit(tex_visitor, *mcmd.texture);
}
else
{
tx[0] = {SamplerName::kSampler0, data_->default_tex};
tx[1] = {SamplerName::kSampler1, palette_tex};
}
const uint8_t* colormap = mcmd.colormap;
Handle<Texture> 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<UniformVariant, 1> g1_uniforms = {{
// Projection
std::array<std::array<float, 4>, 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<UniformVariant, 3> g2_uniforms = {
{// ModelView
std::array<std::array<float, 4>, 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<std::array<float, 3>, 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<int32_t>(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<GraphicsContext> 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<Pipeline> 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();
}

117
src/hwr2/pass_twodee.hpp Normal file
View file

@ -0,0 +1,117 @@
// 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 <memory>
#include <optional>
#include <tuple>
#include <unordered_map>
#include <variant>
#include <vector>
#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<rhi::BindingSet> binding_set = {};
std::optional<std::variant<size_t, MergedTwodeeCommandFlatTexture>> texture;
const uint8_t* colormap;
uint32_t index_offset = 0;
uint32_t elements = 0;
};
struct MergedTwodeeCommandList
{
rhi::Handle<rhi::Buffer> vbo {};
uint32_t vbo_size = 0;
rhi::Handle<rhi::Buffer> ibo {};
uint32_t ibo_size = 0;
std::vector<MergedTwodeeCommand> cmds;
};
std::shared_ptr<TwodeePassData> make_twodee_pass_data();
struct TwodeePass final : public Pass
{
Twodee* ctx_ = nullptr;
std::variant<rhi::Handle<rhi::Texture>, rhi::Handle<rhi::Renderbuffer>> out_color_;
std::shared_ptr<TwodeePassData> data_;
std::shared_ptr<MainPaletteManager> palette_manager_;
std::shared_ptr<FlatTextureManager> flat_manager_;
rhi::Handle<rhi::UniformSet> us_1;
rhi::Handle<rhi::UniformSet> us_2;
std::vector<MergedTwodeeCommandList> cmd_lists_;
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> vbos_;
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> ibos_;
bool rebuild_atlases_ = false;
rhi::Handle<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Texture> 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<rhi::TransferContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
};
} // namespace srb2::hwr2
template <>
struct std::hash<srb2::hwr2::TwodeePipelineKey>
{
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__

114
src/hwr2/twodee.cpp Normal file
View file

@ -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);
}

281
src/hwr2/twodee.hpp Normal file
View file

@ -0,0 +1,281 @@
// 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 <array>
#include <cstdint>
#include <optional>
#include <utility>
#include <variant>
#include <vector>
#include <tcb/span.hpp>
#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
{
kAlphaTransparent,
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::kAlphaTransparent;
lumpnum_t flat_lump = UINT32_MAX; // LUMPERROR but not loading w_wad.h from this header
bool lines = false;
};
using Draw2dCmd = std::variant<Draw2dPatchQuad, Draw2dVertices>;
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<TwodeeVertex> vertices;
std::vector<uint16_t> indices;
std::vector<Draw2dCmd> cmds;
static constexpr const std::size_t kMaxVertices = 65536;
};
class Draw2dQuadBuilder;
class Draw2dVerticesBuilder;
/// @brief Buffered 2D drawing context
class Twodee
{
std::vector<Draw2dList> lists_;
std::vector<TwodeeVertex> current_verts_;
std::vector<uint16_t> 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<Draw2dList>::iterator begin() noexcept { return lists_.begin(); }
typename std::vector<Draw2dList>::iterator end() noexcept { return lists_.end(); }
typename std::vector<Draw2dList>::const_iterator begin() const noexcept { return lists_.cbegin(); }
typename std::vector<Draw2dList>::const_iterator end() const noexcept { return lists_.cend(); }
typename std::vector<Draw2dList>::const_iterator cbegin() const noexcept { return lists_.cbegin(); }
typename std::vector<Draw2dList>::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<std::array<float, 4>> 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__

View file

@ -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);

View file

@ -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 <algorithm>
@ -7,30 +16,51 @@
#include <imgui.h>
#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_screenshot.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;
namespace
{
struct InternalPassData
{
std::shared_ptr<PassManager> resource_passmanager;
std::shared_ptr<PassManager> normal_rendering;
std::shared_ptr<PassManager> wipe_capture_start_rendering;
std::shared_ptr<PassManager> wipe_capture_end_rendering;
std::shared_ptr<PassManager> wipe_rendering;
};
} // namespace
static std::unique_ptr<InternalPassData> g_passes;
static Rhi* g_last_known_rhi = nullptr;
static bool g_imgui_frame_active = false;
Handle<Rhi> 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
@ -48,8 +78,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 +87,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 +117,242 @@ static void finish_legacy_ogl_update()
}
#endif
static InternalPassData build_pass_manager()
{
auto framebuffer_manager = std::make_shared<FramebufferManager>();
auto palette_manager = std::make_shared<MainPaletteManager>();
auto common_resources_manager = std::make_shared<CommonResourcesManager>();
auto flat_texture_manager = std::make_shared<FlatTextureManager>();
auto resource_manager = std::make_shared<PassManager>();
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);
// 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<PassManager>();
auto software_pass = std::make_shared<SoftwarePass>();
auto blit_sw_pass = std::make_shared<BlitRectPass>(palette_manager, true);
auto twodee = std::make_shared<TwodeePass>();
twodee->flat_manager_ = flat_texture_manager;
twodee->data_ = make_twodee_pass_data();
twodee->ctx_ = &g_2d;
auto pp_simple_blit_pass = std::make_shared<BlitRectPass>(false);
auto screenshot_pass = std::make_shared<ScreenshotPass>();
auto imgui_pass = std::make_shared<ImguiPass>();
auto final_composite_pass = std::make_shared<BlitRectPass>(true);
basic_rendering->insert(
"3d_prepare",
[framebuffer_manager](PassManager& mgr, Rhi&)
{
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 && !g_wipeskiprender);
}
);
basic_rendering->insert("software", software_pass);
basic_rendering->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->main_color(), vid.width, vid.height, false, false);
}
);
basic_rendering->insert("blit_sw", blit_sw_pass);
basic_rendering->insert(
"2d_prepare",
[twodee, framebuffer_manager, palette_manager](PassManager& mgr, Rhi&)
{
twodee->output_ = framebuffer_manager->main_color();
twodee->palette_manager_ = palette_manager;
twodee->output_width_ = vid.width;
twodee->output_height_ = vid.height;
}
);
basic_rendering->insert("2d", twodee);
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->main_color(), vid.width, vid.height);
pp_simple_blit_pass
->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, false);
}
);
basic_rendering->insert("pp_final_simple_blit", pp_simple_blit_pass);
basic_rendering->insert(
"screenshot_prepare",
[screenshot_pass, framebuffer_manager](PassManager&, Rhi&)
{
screenshot_pass->set_source(framebuffer_manager->current_post_color(), vid.width, vid.height);
}
);
basic_rendering->insert("screenshot", screenshot_pass);
// 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<PassManager>();
composite_present_rendering->insert(
"final_composite_prepare",
[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, false);
}
);
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)
{
g_imgui_frame_active = false;
rhi.present();
rhi.finish();
framebuffer_manager->reset_post();
}
);
// Normal rendering combines basic rendering and composite-present.
auto normal_rendering = std::make_shared<PassManager>();
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<PassManager>();
auto wipe_start_blit = std::make_shared<BlitRectPass>();
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<PassManager>();
auto wipe_end_blit = std::make_shared<BlitRectPass>();
auto wipe_end_blit_start_to_main = std::make_shared<BlitRectPass>();
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, 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<PassManager>();
auto pp_wipe_pass = std::make_shared<PostprocessWipePass>();
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<Texture> start = framebuffer_manager->main_color();
Handle<Texture> 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(
"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;
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)
{
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)
{
if (rhi_changed(rhi) || !g_passes)
{
g_last_known_rhi = rhi;
g_passes = std::make_unique<InternalPassData>(build_pass_manager());
}
}
void I_FinishUpdate(void)
{
if (rendermode == render_none)
@ -106,19 +368,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())
if (rhi == nullptr)
{
// reinitialize passes
g_sw_pass = SoftwareBlitPass();
g_imgui_pass = ImguiPass();
// ???
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)
@ -127,48 +404,63 @@ void I_FinishUpdate(void)
return;
}
// Prepare phase
if (rendermode == render_soft)
{
g_sw_pass.prepass(*rhi);
}
g_imgui_pass.prepass(*rhi);
maybe_reinit_passes(rhi);
// Transfer phase
Handle<TransferContext> 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<GraphicsContext> 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_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);
}

View file

@ -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] = {

View file

@ -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

View file

@ -474,10 +474,10 @@ 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)))
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)
@ -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)
{

View file

@ -25,6 +25,7 @@ void K_InitDirector(void)
{
INT32 playernum;
directorinfo.active = false;
directorinfo.cooldown = SWITCHTIME;
directorinfo.freeze = 0;
directorinfo.attacker = 0;
@ -114,6 +115,11 @@ static boolean K_CanSwitchDirector(void)
static void K_DirectorSwitch(INT32 player, boolean force)
{
if (!directorinfo.active)
{
return;
}
if (P_IsDisplayPlayer(&players[player]))
{
return;
@ -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) {
@ -284,14 +285,26 @@ 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);
}
// 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);
}
@ -299,3 +312,13 @@ void K_UpdateDirector(void)
break;
}
}
void K_ToggleDirector(boolean active)
{
if (directorinfo.active != active)
{
directorinfo.cooldown = 0; // switch immediately
}
directorinfo.active = active;
}

View file

@ -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"

View file

@ -4639,6 +4639,77 @@ K_drawMiniPing (void)
}
}
static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2], INT32 textflags)
{
INT32 flags = V_SNAPTORIGHT | V_SLIDEIN | V_SPLITSCREEN;
const UINT8 anim_duration = 16;
const UINT8 anim = (leveltime % (anim_duration * 2)) < anim_duration;
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);
}
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;
}
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;
}
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)
{
itemroulette_t rouletteData = {0};
@ -4957,6 +5028,11 @@ void K_drawKartHUD(void)
K_drawMiniPing();
}
if (displayplayers[viewnum] != g_localplayers[viewnum] && !demo.playback)
{
K_drawDirectorHUD();
}
if (cv_kartdebugdistribution.value)
K_drawDistributionDebugger();

View file

@ -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
@ -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);
}
@ -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;
@ -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);
@ -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--;
@ -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)

View file

@ -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)
@ -556,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;
}
@ -4908,7 +4905,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;

View file

@ -602,7 +602,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);
}
}
@ -1035,7 +1035,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);

View file

@ -2,6 +2,8 @@
#ifndef k_objects_H
#define k_objects_H
#include "taglist.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -92,6 +94,13 @@ boolean Obj_ItemSpotIsAvailable(const mobj_t *spot);
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
} // extern "C"
#endif

View file

@ -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)
@ -813,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;

View file

@ -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}
};

View file

@ -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);
}
//

View file

@ -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

View file

@ -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<RGBA_t> pal(&pLocalPalette[std::max(st_palette, 0) * 256], 256);
tcb::span<uint8_t> 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));
}

View file

@ -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,

440
src/m_memcpy.c Normal file
View file

@ -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
}

View file

@ -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 <unistd.h>
#endif
#include <algorithm>
#include <errno.h>
// Extended map support.
@ -44,7 +45,10 @@
#include "command.h" // cv_execversion
#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...
#include "k_menu.h"
@ -308,7 +312,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<UINT8*>(Z_Malloc(length + 1, tag, NULL));
count = fread(buf, 1, length, handle);
fclose(handle);
@ -472,7 +476,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 +510,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 +778,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 +858,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_colorp>(png_malloc(png_ptr, sizeof(png_color)*256)); //palette
png_uint_16 i;
const png_byte *pal = palette;
@ -975,8 +979,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<const png_info*>(png_info_ptr));
png_bytepp row_pointers = static_cast<png_bytepp>(png_malloc(png_ptr, height* sizeof (png_bytep)));
png_uint_32 y;
for (y = 0; y < height; y++)
{
@ -1298,6 +1302,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;
@ -1313,6 +1320,7 @@ static inline moviemode_t M_StartMovieAVRecorder(const char *pathname)
}
return MM_AVRECORDER;
#endif
}
void M_StartMovie(void)
@ -1334,7 +1342,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);
}
@ -1365,36 +1373,45 @@ 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
}
void M_SaveFrame(void)
static void M_SaveFrame_AVRecorder(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
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;
#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
// skip interpolated frames for other modes
if (oldtic == I_GetTime())
@ -1444,6 +1461,15 @@ void M_SaveFrame(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:
@ -1452,6 +1478,64 @@ void M_SaveFrame(void)
#endif
}
static void M_SaveFrame_GIF(uint32_t width, uint32_t height, tcb::span<const std::byte> 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<const uint8_t*>(data.data()));
}
static void M_SaveFrame_AVRecorder(uint32_t width, uint32_t height, tcb::span<const std::byte> 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<const uint8_t*>(data.data());
auto data_end = reinterpret_cast<const uint8_t*>(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<const std::byte> 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
@ -1484,9 +1568,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;
}
@ -1508,7 +1594,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;
@ -1579,7 +1665,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, (png_bytep)data);
png_write_end(png_ptr, png_info_ptr);
png_destroy_write_struct(&png_ptr, &png_info_ptr);
@ -1687,19 +1773,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<const std::byte> 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;
@ -1719,7 +1810,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);
}
@ -1732,13 +1823,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;
@ -1749,9 +1833,9 @@ void M_DoScreenShot(void)
else
#endif
{
M_CreateScreenShotPalette();
const void* pixel_data = static_cast<const void*>(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
@ -2462,462 +2546,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 +2686,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);
}
}

View file

@ -22,6 +22,14 @@
#include "command.h"
#ifdef __cplusplus
#include <cstddef>
#include <tcb/span.hpp>
void M_DoScreenShot(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
void M_SaveFrame(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
extern "C" {
#endif
@ -41,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
@ -82,12 +90,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);

View file

@ -58,17 +58,16 @@ public:
};
// TODO: remove once hwr2 twodee is finished
struct IndexedVideoFrame
struct StagingVideoFrame
{
using instance_t = std::unique_ptr<IndexedVideoFrame>;
using instance_t = std::unique_ptr<StagingVideoFrame>;
std::array<RGBA_t, 256> palette;
std::vector<uint8_t> 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;

View file

@ -53,7 +53,7 @@ public:
template <typename _>
struct Traits<VideoEncoder, _>
{
using frame_type = IndexedVideoFrame::instance_t;
using frame_type = StagingVideoFrame::instance_t;
};
std::vector<typename Traits<T>::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 <>

View file

@ -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<uint32_t*>(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<int> 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<IndexedVideoFrame>(width, height, *pts);
return std::make_unique<StagingVideoFrame>(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();

View file

@ -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));
}

View file

@ -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 = {

View file

@ -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);

View file

@ -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));

View file

@ -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);

View file

@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE
ufo.c
monitor.c
item-spot.c
loops.c
)

281
src/objects/loops.c Normal file
View file

@ -0,0 +1,281 @@
// 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 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,
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);
}
}
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)
{
const mapthing_t *mt = center->spawnpoint;
center_max_revolution(center) = mt->args[1] * FRACUNIT / 360;
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,
&center_alpha(center),
&center_beta(center));
break;
case TMLOOP_BETA:
crisscross(anchor,
&center_beta(center),
&center_alpha(center));
break;
}
}
}
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;
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;
@ -1911,6 +1915,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;

View file

@ -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);

180
src/p_loop.c Normal file
View file

@ -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;
}

View file

@ -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
@ -931,8 +932,9 @@ 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_DROPTARGET
|| (thing->type == MT_PLAYER && tm.thing->target != thing)))
{
// see if it went over / under
@ -967,7 +969,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)))
{
@ -2674,6 +2676,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 +2752,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);

View file

@ -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);
@ -1790,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*/
@ -1810,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;
}
@ -3978,7 +3987,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);
}
@ -4001,7 +4011,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);
@ -9742,14 +9753,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);
@ -11827,6 +11830,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)
@ -13316,6 +13335,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
mobj->reactiontime++;
break;
}
case MT_LOOPCENTERPOINT:
{
Obj_InitLoopCenter(mobj);
break;
}
default:
break;
}
@ -13536,6 +13560,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;
@ -13554,6 +13583,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];
@ -13561,15 +13605,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;
}
}
@ -13717,6 +13770,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.

View file

@ -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);

View file

@ -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
}
}

View file

@ -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: ");
@ -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';
@ -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,20 +705,32 @@ 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++)
{
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
case 2021: // MT_LOOPCENTERPOINT
continue; // These were already spawned
}
if (mt->type == mobjinfo[MT_BATTLECAPSULE].doomednum)
continue; // This will spawn later
@ -713,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
@ -720,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!
@ -3928,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:
@ -5952,6 +6007,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;
}
@ -6732,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;
@ -7499,9 +7566,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");
@ -7514,6 +7586,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);
@ -7550,7 +7623,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)
@ -7565,8 +7638,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
I_UpdateTime(cv_timescale.value); \
} \
lastwipetic = nowtime; \
if (moviemode) \
M_SaveFrame(); \
if (moviemode && rendermode == render_opengl) \
M_LegacySaveFrame(); \
NetKeepAlive(); \
} \
@ -7579,7 +7652,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();
@ -7587,7 +7660,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);
@ -7595,8 +7668,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);
}
}
@ -7620,6 +7693,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.
@ -7649,10 +7723,12 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
if (ranspecialwipe != 2)
S_StartSound(NULL, sfx_s3kaf);
levelfadecol = 0;
wipetype = wipe_encore_towhite;
}
else if (encoremode)
{
levelfadecol = 0;
wipetype = wipe_encore_towhite;
}
else
{
@ -7667,7 +7743,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;*/

View file

@ -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.

View file

@ -647,24 +647,39 @@ 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)
{
if (leveltime == (starttime + (TICRATE/2)))
// Start countdown/music handling
if (leveltime == starttime-(3*TICRATE))
{
S_StartSound(NULL, sfx_s3ka7); // 3,
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))
{
S_StartSound(NULL, sfx_s3ka7); // 2, 1,
}
else if (leveltime == starttime)
{
S_StartSound(NULL, sfx_s3kad); // GO!
}
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.

View file

@ -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;
@ -4227,6 +4225,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.

View file

@ -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]])
{

View file

@ -103,6 +103,7 @@ void Patch_Free(patch_t *patch)
{
if (!patch || patch == missingpat)
return;
Patch_FreeData(patch);
Z_Free(patch);
}

View file

@ -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 <memory>
@ -13,7 +22,7 @@
using namespace srb2;
using namespace rhi;
#if 1
#ifndef NDEBUG
#define GL_ASSERT \
{ \
GLenum __err = gl_->GetError(); \
@ -33,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:
@ -56,6 +71,16 @@ constexpr std::tuple<GLenum, GLenum, GLuint> 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::kRGB8:
layout = GL_RGB;
type = GL_UNSIGNED_BYTE;
size = 3;
break;
case rhi::PixelFormat::kRGBA8:
layout = GL_RGBA;
type = GL_UNSIGNED_BYTE;
@ -77,6 +102,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 +332,35 @@ 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";
case rhi::UniformName::kWipeColorizeMode:
return "u_wipe_colorize_mode";
case rhi::UniformName::kWipeEncoreSwizzle:
return "u_wipe_encore_swizzle";
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";
case rhi::UniformName::kWipeColorizeMode:
return "ENABLE_U_WIPE_COLORIZE_MODE";
case rhi::UniformName::kWipeEncoreSwizzle:
return "ENABLE_U_WIPE_ENCORE_SWIZZLE";
default:
return nullptr;
}
@ -308,6 +383,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 +515,13 @@ rhi::Handle<rhi::Texture> 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 +536,7 @@ rhi::Handle<rhi::Texture> 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 +575,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 +843,79 @@ rhi::Handle<rhi::Pipeline> 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<GLint>(vert_src_processed.size())};
const char* frag_src_arr[1] = {frag_src.c_str()};
const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src.size())};
const char* frag_src_arr[1] = {frag_src_processed.c_str()};
const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src_processed.size())};
vertex = gl_->CreateShader(GL_VERTEX_SHADER);
gl_->ShaderSource(vertex, 1, vert_src_arr, vert_src_arr_lens);
@ -1380,6 +1548,8 @@ void GlCoreRhi::bind_index_buffer(Handle<GraphicsContext> ctx, Handle<Buffer> bu
SRB2_ASSERT(ib.desc.type == rhi::BufferType::kIndexBuffer);
current_index_buffer_ = buffer;
gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer);
}
@ -1412,22 +1582,37 @@ void GlCoreRhi::draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_
void GlCoreRhi::draw_indexed(Handle<GraphicsContext> 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<const void*>(first_index * 2 + index_buffer_offset_)
(const void*)((size_t)first_index * 2 + index_buffer_offset_)
);
GL_ASSERT
}
void GlCoreRhi::read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, tcb::span<std::byte> out)
void GlCoreRhi::read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> 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<GLenum, GLenum, GLuint> 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()

View file

@ -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<GlCoreUniformSet> uniform_set_slab_;
Slab<GlCoreBindingSet> binding_set_slab_;
Handle<Buffer> current_index_buffer_;
std::unordered_map<GlCoreFramebufferKey, uint32_t> framebuffers_ {16};
struct DefaultRenderPassState
@ -215,7 +226,8 @@ public:
virtual void set_viewport(Handle<GraphicsContext> ctx, const Rect& rect) override;
virtual void draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex) override;
virtual void draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) override;
virtual void read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, tcb::span<std::byte> out) override;
virtual void
read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) override;
virtual void present() override;

View file

@ -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 <memory>

View file

@ -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__

View file

@ -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__

View file

@ -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 <exception>
@ -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,22 @@ 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::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
{
@ -35,6 +58,8 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram
return kProgramRequirementsUnshaded;
case PipelineProgram::kUnshadedPaletted:
return kProgramRequirementsUnshadedPaletted;
case PipelineProgram::kPostprocessWipe:
return kProgramRequirementsPostprocessWipe;
default:
std::terminate();
}

View file

@ -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,8 @@ enum class UniformFormat
enum class PixelFormat
{
kR8,
kRG8,
kRGB8,
kRGBA8,
kDepth16,
kStencil8
@ -71,8 +82,10 @@ enum class PixelFormat
enum class TextureFormat
{
kLuminance,
kLuminanceAlpha,
kRGB,
kRGBA
kRGBA,
kDepth
};
enum class CompareFunc
@ -152,7 +165,8 @@ enum class AttachmentStoreOp
enum class PipelineProgram
{
kUnshaded,
kUnshadedPaletted
kUnshadedPaletted,
kPostprocessWipe
};
enum class BufferType
@ -181,7 +195,10 @@ enum class UniformName
kTime,
kModelView,
kProjection,
kTexCoord0Transform
kTexCoord0Transform,
kSampler0IsIndexedAlpha,
kWipeColorizeMode,
kWipeEncoreSwizzle
};
enum class SamplerName
@ -237,12 +254,12 @@ struct ProgramVertexInputRequirements
struct ProgramUniformRequirements
{
srb2::StaticVec<srb2::StaticVec<UniformName, 16>, 4> uniform_groups;
srb2::StaticVec<srb2::StaticVec<ProgramUniformInput, 16>, 4> uniform_groups;
};
struct ProgramSamplerRequirements
{
std::array<std::optional<ProgramSamplerInput>, kMaxSamplers> samplers;
srb2::StaticVec<ProgramSamplerInput, kMaxSamplers> samplers;
};
struct ProgramRequirements
@ -254,6 +271,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 +306,12 @@ inline constexpr const UniformFormat uniform_format(UniformName name) noexcept
return UniformFormat::kMat4;
case UniformName::kTexCoord0Transform:
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;
}
@ -309,8 +333,8 @@ struct VertexAttributeLayoutDesc
struct VertexInputDesc
{
std::vector<VertexBufferLayoutDesc> buffer_layouts;
std::vector<VertexAttributeLayoutDesc> attr_layouts;
srb2::StaticVec<VertexBufferLayoutDesc, 4> buffer_layouts;
srb2::StaticVec<VertexAttributeLayoutDesc, 8> attr_layouts;
};
struct UniformInputDesc
@ -489,6 +513,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
{
@ -542,7 +569,8 @@ struct Rhi
virtual void set_viewport(Handle<GraphicsContext> ctx, const Rect& rect) = 0;
virtual void draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex) = 0;
virtual void draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) = 0;
virtual void read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, tcb::span<std::byte> out) = 0;
virtual void
read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) = 0;
virtual void present() = 0;

View file

@ -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<uint32_t>(width);

View file

@ -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<2>> gain_music;
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
static shared_ptr<srb2::media::AVRecorder> 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
}

View file

@ -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 <SDL.h>
@ -33,6 +42,10 @@ std::tuple<std::string, std::string> 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();
}

View file

@ -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__

View file

@ -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 <SDL.h>

View file

@ -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__

View file

@ -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");

View file

@ -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;

Some files were not shown because too many files have changed in this diff Show more