mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2026-02-03 20:26:05 +00:00
Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into conditions-cascading
This commit is contained in:
commit
92285b90fa
110 changed files with 6773 additions and 1648 deletions
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
|||
25
src/d_main.c
25
src/d_main.c
|
|
@ -343,7 +343,7 @@ static void D_Display(void)
|
|||
F_WipeStartScreen();
|
||||
F_WipeColorFill(31);
|
||||
F_WipeEndScreen();
|
||||
F_RunWipe(wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false);
|
||||
F_RunWipe(wipedefindex, wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false);
|
||||
}
|
||||
|
||||
if (gamestate != GS_LEVEL && rendermode != render_none)
|
||||
|
|
@ -356,7 +356,7 @@ static void D_Display(void)
|
|||
}
|
||||
else //dedicated servers
|
||||
{
|
||||
F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false);
|
||||
F_RunWipe(wipedefindex, wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false);
|
||||
wipegamestate = gamestate;
|
||||
}
|
||||
|
||||
|
|
@ -633,7 +633,7 @@ static void D_Display(void)
|
|||
{
|
||||
F_WipeEndScreen();
|
||||
|
||||
F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false);
|
||||
F_RunWipe(wipedefindex, wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false);
|
||||
}
|
||||
|
||||
// reset counters so timedemo doesn't count the wipe duration
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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[] = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
386
src/f_wipe.c
386
src/f_wipe.c
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
112
src/g_game.c
112
src/g_game.c
|
|
@ -61,6 +61,7 @@
|
|||
#include "k_specialstage.h"
|
||||
#include "k_bot.h"
|
||||
#include "doomstat.h"
|
||||
#include "k_director.h"
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
#include "discord.h"
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
209
src/hwr2/pass_blit_rect.cpp
Normal 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)
|
||||
{
|
||||
}
|
||||
93
src/hwr2/pass_blit_rect.hpp
Normal file
93
src/hwr2/pass_blit_rect.hpp
Normal 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__
|
||||
|
|
@ -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});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
191
src/hwr2/pass_manager.cpp
Normal 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
65
src/hwr2/pass_manager.hpp
Normal 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__
|
||||
243
src/hwr2/pass_postprocess.cpp
Normal file
243
src/hwr2/pass_postprocess.cpp
Normal 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();
|
||||
}
|
||||
70
src/hwr2/pass_postprocess.hpp
Normal file
70
src/hwr2/pass_postprocess.hpp
Normal 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__
|
||||
285
src/hwr2/pass_resource_managers.cpp
Normal file
285
src/hwr2/pass_resource_managers.cpp
Normal 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;
|
||||
}
|
||||
150
src/hwr2/pass_resource_managers.hpp
Normal file
150
src/hwr2/pass_resource_managers.hpp
Normal 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__
|
||||
77
src/hwr2/pass_screenshot.cpp
Normal file
77
src/hwr2/pass_screenshot.cpp
Normal 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;
|
||||
}
|
||||
49
src/hwr2/pass_screenshot.hpp
Normal file
49
src/hwr2/pass_screenshot.hpp
Normal 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__
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
945
src/hwr2/pass_twodee.cpp
Normal 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
117
src/hwr2/pass_twodee.hpp
Normal 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
114
src/hwr2/twodee.cpp
Normal 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
281
src/hwr2/twodee.hpp
Normal 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__
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
54
src/info.c
54
src/info.c
|
|
@ -29311,6 +29311,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
|
|||
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP|MF_NOSQUISH, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_LOOPENDPOINT
|
||||
2020, // doomednum
|
||||
S_INVISIBLE, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
8, // reactiontime
|
||||
sfx_None, // attacksound
|
||||
S_NULL, // painstate
|
||||
0, // painchance
|
||||
sfx_None, // painsound
|
||||
S_NULL, // meleestate
|
||||
S_NULL, // missilestate
|
||||
S_NULL, // deathstate
|
||||
S_NULL, // xdeathstate
|
||||
sfx_None, // deathsound
|
||||
0, // speed
|
||||
MAXRADIUS, // radius
|
||||
2*MAXRADIUS, // height
|
||||
0, // display offset
|
||||
100, // mass
|
||||
1, // damage
|
||||
sfx_None, // activesound
|
||||
MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
|
||||
{ // MT_LOOPCENTERPOINT
|
||||
2021, // doomednum
|
||||
S_INVISIBLE, // spawnstate
|
||||
1000, // spawnhealth
|
||||
S_NULL, // seestate
|
||||
sfx_None, // seesound
|
||||
8, // reactiontime
|
||||
sfx_None, // attacksound
|
||||
S_NULL, // painstate
|
||||
0, // painchance
|
||||
sfx_None, // painsound
|
||||
S_NULL, // meleestate
|
||||
S_NULL, // missilestate
|
||||
S_NULL, // deathstate
|
||||
S_NULL, // xdeathstate
|
||||
sfx_None, // deathsound
|
||||
0, // speed
|
||||
48*FRACUNIT, // radius
|
||||
32*FRACUNIT, // height
|
||||
0, // display offset
|
||||
100, // mass
|
||||
1, // damage
|
||||
sfx_None, // activesound
|
||||
MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
|
||||
S_NULL // raisestate
|
||||
},
|
||||
};
|
||||
|
||||
skincolor_t skincolors[MAXSKINCOLORS] = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
76
src/k_hud.c
76
src/k_hud.c
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
32
src/k_kart.c
32
src/k_kart.c
|
|
@ -5625,7 +5625,7 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter)
|
|||
|
||||
spd = FixedMul(82 * punter->scale, K_GetKartGameSpeedScalar(gamespeed)); // Avg Speed is 41 in Normal
|
||||
|
||||
mine->flags |= MF_NOCLIPTHING;
|
||||
mine->flags |= (MF_NOCLIP|MF_NOCLIPTHING);
|
||||
|
||||
P_SetMobjState(mine, S_SSMINE_AIR1);
|
||||
mine->threshold = 10;
|
||||
|
|
@ -5637,7 +5637,7 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter)
|
|||
|
||||
//K_SetHitLagForObjects(punter, mine, 5);
|
||||
|
||||
mine->flags &= ~MF_NOCLIPTHING;
|
||||
mine->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING);
|
||||
}
|
||||
|
||||
#define THUNDERRADIUS 320
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
440
src/m_memcpy.c
Normal 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
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
16
src/m_misc.h
16
src/m_misc.h
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
ufo.c
|
||||
monitor.c
|
||||
item-spot.c
|
||||
loops.c
|
||||
)
|
||||
|
|
|
|||
281
src/objects/loops.c
Normal file
281
src/objects/loops.c
Normal 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,
|
||||
¢er_alpha(center),
|
||||
¢er_beta(center));
|
||||
break;
|
||||
|
||||
case TMLOOP_BETA:
|
||||
crisscross(anchor,
|
||||
¢er_beta(center),
|
||||
¢er_alpha(center));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Obj_LoopEndpointCollide
|
||||
( mobj_t * end,
|
||||
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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
180
src/p_loop.c
Normal 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;
|
||||
}
|
||||
24
src/p_map.c
24
src/p_map.c
|
|
@ -917,6 +917,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|
|||
|| tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG
|
||||
|| tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK
|
||||
|| tm.thing->type == MT_GARDENTOP
|
||||
|| tm.thing->type == MT_DROPTARGET
|
||||
|| (tm.thing->type == MT_PLAYER && thing->target != tm.thing)))
|
||||
{
|
||||
// see if it went over / under
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
87
src/p_mobj.c
87
src/p_mobj.c
|
|
@ -46,6 +46,7 @@
|
|||
#include "k_collide.h"
|
||||
#include "k_objects.h"
|
||||
#include "k_grandprix.h"
|
||||
#include "k_director.h"
|
||||
|
||||
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
|
||||
consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
104
src/p_setup.c
104
src/p_setup.c
|
|
@ -334,7 +334,7 @@ FUNCNORETURN static ATTRNORETURN void CorruptMapError(const char *msg)
|
|||
{
|
||||
sprintf(mapname, "ID %d", gamemap-1);
|
||||
}
|
||||
|
||||
|
||||
CON_LogMessage("Map ");
|
||||
CON_LogMessage(mapname);
|
||||
CON_LogMessage(" is corrupt: ");
|
||||
|
|
@ -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;*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
23
src/p_tick.c
23
src/p_tick.c
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
12
src/r_main.c
12
src/r_main.c
|
|
@ -1476,9 +1476,15 @@ void R_RenderPlayerView(void)
|
|||
if (cv_homremoval.value && player == &players[displayplayers[0]])
|
||||
{
|
||||
if (cv_homremoval.value == 1)
|
||||
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // No HOM effect!
|
||||
else //'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
|
||||
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 32+(timeinmap&15));
|
||||
{
|
||||
// Clear the software screen buffer to remove HOM
|
||||
memset(screens[0], 31, vid.width * vid.height * vid.bpp);
|
||||
}
|
||||
else
|
||||
{
|
||||
//'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
|
||||
memset(screens[0], 32+(timeinmap&15), vid.width * vid.height * vid.bpp);
|
||||
}
|
||||
}
|
||||
else if (r_splitscreen == 2 && player == &players[displayplayers[2]])
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ void Patch_Free(patch_t *patch)
|
|||
{
|
||||
if (!patch || patch == missingpat)
|
||||
return;
|
||||
|
||||
Patch_FreeData(patch);
|
||||
Z_Free(patch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue