Skip to content

Commit

Permalink
Merge pull request #160 from Daggolin/remaps
Browse files Browse the repository at this point in the history
Improved Shader Remaps
  • Loading branch information
Daggolin committed Sep 26, 2023
2 parents 036d2da + 626d5c3 commit 7dc8400
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 57 deletions.
4 changes: 4 additions & 0 deletions src/client/cl_cgame.cpp
Expand Up @@ -452,6 +452,10 @@ void CL_ConfigstringModified( void ) {
CL_SystemInfoChanged();
}

// Shader Remaps
if ( index == cls.cs_remaps ) {
CL_ShaderStateChanged();
}
}


Expand Down
200 changes: 200 additions & 0 deletions src/client/cl_main.cpp
Expand Up @@ -1324,6 +1324,9 @@ void CL_Vid_Restart_f( void ) {
// send pure checksums
CL_SendPureChecksums();
}

// Reapply mvremaps to override classic ones
CL_ShaderStateChanged();
}

/*
Expand Down Expand Up @@ -2788,6 +2791,50 @@ void CL_SetForcePowers_f( void ) {
#define G2_VERT_SPACE_CLIENT_SIZE 256
#endif


/*
=================
MV_UpdateClFlags
Called by CL_Init. Updates the mv_clFlags accoding to the current settings
At the time of initial implementation there is only two clFlags, one which is always active and one which could be determined at compile time, so the function is not required, yet.
However in future versions users might be able to disable some of the features, so the clFlags need to be adjusted in such cases
=================
*/
void MV_UpdateClFlags( void )
{
// mv_clFlags - Used to inform the server about available jk2mv clientside features
static cvar_t *mv_clFlags;
char *value;
int intValue = 0;

// Check for the features and determine the flags
if ( MV_APILEVEL >= 4 ) intValue |= MV_CLFLAG_SUBMODEL_BYPASS;
intValue |= MV_CLFLAG_ADVANCED_REMAPS;

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! Forks of JK2MV should NOT modify the mv_clFlags !!!
// !!! Removal, replacement or adding of new flags might lead to incompatibilities !!!
// !!! Forks should define their own userinfo cvar instead of modifying this !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

// If the current clFlags match the intValue we can return
if ( mv_clFlags && mv_clFlags->integer == intValue ) return;

// We need a string when registering/setting the cvar
value = va( "%i", intValue );

if ( !mv_clFlags )
{ // Register the cvar as rom, internal and userinfo for the server to see, but without users manually changing it
mv_clFlags = Cvar_Get( "mv_clFlags", value, CVAR_ROM|CVAR_INTERNAL|CVAR_USERINFO );
}
else
{ // Update the cvar
Cvar_Set( "mv_clFlags", value );
}
}

/*
====================
CL_Init
Expand Down Expand Up @@ -2917,6 +2964,9 @@ void CL_Init( void ) {
cl_downloadTime = Cvar_Get("cl_downloadTime", "", CVAR_INTERNAL);
cl_downloadProtocol = Cvar_Get("cl_downloadProtocol", "", CVAR_INTERNAL);

// Update cl flags userinfo
MV_UpdateClFlags();

//
// register our commands
//
Expand Down Expand Up @@ -4058,3 +4108,153 @@ void CL_GetVMGLConfig(vmglconfig_t *vmglconfig) {
vmglconfig->stereoEnabled = cls.glconfig.stereoEnabled;
vmglconfig->smpActive = cls.glconfig.smpActive;
}

void CL_ShaderStateChanged( void ) {
// Originally copied from CG_ShaderStateChanged, but rewritten to avoid issues of the original implementation and to
// support settings
char originalShader[MAX_QPATH];
char newShader[MAX_QPATH];
char settings[32];
char *timeOffset;
char *lightmapMode;
char *styleMode;

shaderRemapLightmapType_t lightmapModeValue;
shaderRemapStyleType_t styleModeValue;

const char *curPos;
const char *endPos;

int length;

// Make sure it's a valid configstring index
if ( cls.cs_remaps < CS_SYSTEMINFO || cls.cs_remaps >= MAX_CONFIGSTRINGS ) return;

// Clear any active remaps. We are going to reapply those that should stay below when parsing the string anyway.
re.RemoveAdvancedRemaps();

curPos = cl.gameState.stringData + cl.gameState.stringOffsets[ cls.cs_remaps ];
endPos = curPos + strlen( curPos );

// Handling of these is really ugly. I originally wanted to handle them like the base game/cgame modules do, but
// those are prone to injections. Summary of delimeter issues:
// - base uses '=', ':' an '@', all of them can be used in shader names
// - .shader files don't seem to support spaces in shader names, but texture paths support spaces
// - .shader files allow most other characters, including backspace '\'
// -> using a delimeter is not a reliable option

// The next best thing seemed to be to give the length of the shader before its name to allow all characters to be
// used. As I don't want to waste any space (configstrings containing shader paths are wasteful as it is), I decided
// to encode the length in a single byte (MAX_QPATH is 80, so a single byte can easily hold the length). However the
// netcode and the handling of quotes lead to other issues:
// - 34 maps to 32, thus 32 could mean either of these numbers while 34 doesn't exist
// - 37 and >127 map to 46, thus 37 doesn't exist and 46 could mean 37, 46 or >127
// -> the biggest amount of consecutive numbers without multiple meanings spans from 47 to 127; as MAX_QPATH is 80
// this should fix exactly
// -> could also use the range from 38 to 127, because - when isolating the range - there are no duplicates within
// it; this would allow for a size of up to 89, which is unlikely to be useful if MAX_QPATH ever gets increased
// -> using 47 to 127

// Example input: "6console4clear929300;p;p;":
// - "6" -> ascii 54 -> 54 - 47 = 7 -> the next 7 characters are the source shader name
// - "console" -> source shader (7 characters)
// - "4" -> ascii 52 -> 52 - 47 = 5 -> the next 5 characters are the destination shader name
// - "clear" -> destination shader (5 characters)
// - "9" -> ascii 57 -> 57 - 47 = 10 -> the next 10 characters are the options
// - "29300;p;p;"
// - "29300" -> timeOffset
// - "p" -> lightmapMode
// - "p" -> styleMode

// Parse configstring
while ( curPos < endPos )
{
// Read original shader
length = *(curPos++);
length -= 47; // Length is increased by 47 to workaround netchan limits
if ( length < 1 )
{
Com_DPrintf( "CL_ShaderStateChanged: invalid length encoding for source shader\n" );
break;
}
Q_strncpyz( originalShader, curPos, MIN((unsigned int)length+1, sizeof(originalShader)) );
curPos += length;

// Read new shader
if ( curPos >= endPos ) break;
length = *(curPos++);
length -= 47; // Length is increased by 47 to workaround netchan limits
if ( length < 1 )
{
Com_DPrintf( "CL_ShaderStateChanged: invalid length encoding for destination shader\n" );
break;
}
Q_strncpyz( newShader, curPos, MIN((unsigned int)length+1, sizeof(newShader)) );
curPos += length;

// Read settings
if ( curPos >= endPos ) break;
length = *(curPos++);
length -= 47; // Length is increased by 47 to workaround netchan limits
if ( length < 1 )
{
Com_DPrintf( "CL_ShaderStateChanged: invalid length encoding for settings\n" );
break;
}
Q_strncpyz( settings, curPos, MIN((unsigned int)length+1, sizeof(settings)) );
curPos += length;

// Get values from settings
timeOffset = strtok( settings, ";" );
lightmapMode = strtok( NULL, ";" );
styleMode = strtok( NULL, ";" );

// Pick lightmap value
if ( lightmapMode && *lightmapMode )
{
switch( *lightmapMode )
{
case 'p': // preserve
lightmapModeValue = SHADERREMAP_LIGHTMAP_PRESERVE;
break;
case 'n': // none
lightmapModeValue = SHADERREMAP_LIGHTMAP_NONE;
break;
case 'f': // fullbright
lightmapModeValue = SHADERREMAP_LIGHTMAP_FULLBRIGHT;
break;
case 'v': // vertex
lightmapModeValue = SHADERREMAP_LIGHTMAP_VERTEX;
break;
case '2': // 2d
lightmapModeValue = SHADERREMAP_LIGHTMAP_2D;
break;
default: // fallback to preserve
lightmapModeValue = SHADERREMAP_LIGHTMAP_PRESERVE;
break;
}
}
else lightmapModeValue = SHADERREMAP_LIGHTMAP_PRESERVE; // fallback to preserve

// Pick style value
if ( styleMode && *styleMode )
{
switch( *styleMode )
{
case 'p': // preserve
styleModeValue = SHADERREMAP_STYLE_PRESERVE;
break;
case 'd': // default style
styleModeValue = SHADERREMAP_STYLE_DEFAULT;
break;
default: // fallback to preserve
styleModeValue = SHADERREMAP_STYLE_PRESERVE;
break;
}
}
else styleModeValue = SHADERREMAP_STYLE_PRESERVE; // fallback to preserve

// Apply remap
re.RemapShaderAdvanced( originalShader, newShader, timeOffset ? atoi(timeOffset) : 0, lightmapModeValue, styleModeValue );
}
}
10 changes: 10 additions & 0 deletions src/client/cl_parse.cpp
Expand Up @@ -319,6 +319,7 @@ void CL_SystemInfoChanged( void ) {
char key[BIG_INFO_KEY];
char value[BIG_INFO_VALUE];
qboolean gameSet;
int old_cs_remaps = cls.cs_remaps;

systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
Expand All @@ -327,6 +328,12 @@ void CL_SystemInfoChanged( void ) {
t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" );
FS_PureServerSetReferencedPaks( s, t );

cls.cs_remaps = atoi( Info_ValueForKey(systemInfo, "mv_cs_remaps") );
if ( cls.cs_remaps != old_cs_remaps ) {
// If the configstring changed remove any active advanced remaps
re.RemoveAdvancedRemaps();
}

// don't set any other vars when playing a demo
if ( clc.demoplaying ) {
return;
Expand Down Expand Up @@ -485,6 +492,9 @@ void CL_ParseGamestate( msg_t *msg ) {
// parse serverId and other cvars
CL_SystemInfoChanged();

// Shader Remaps
CL_ShaderStateChanged();

// reinitialize the filesystem if the game directory has changed
if( FS_ConditionalRestart( clc.checksumFeed ) ) {
// don't set to true because we yet have to start downloading
Expand Down
3 changes: 3 additions & 0 deletions src/client/client.h
Expand Up @@ -352,6 +352,8 @@ typedef struct {

int fixes;
qboolean submodelBypass;

int cs_remaps;
} clientStatic_t;

#define CON_TEXTSIZE 131072 // increased in jk2mv
Expand Down Expand Up @@ -502,6 +504,7 @@ int CL_ServerStatus( const char *serverAddress, char *serverStatusString, int ma

void CL_GetVMGLConfig(vmglconfig_t *vmglconfig);
int CL_ScaledMilliseconds(void);
void CL_ShaderStateChanged( void );

//
// cl_input
Expand Down
4 changes: 4 additions & 0 deletions src/game/bg_public.h
Expand Up @@ -42,6 +42,10 @@

#define MAX_CLIENT_SCORE_SEND 20

// mv_clflags - set by the client engine in the mv_clFlags userinfo cvar to inform the server about additional features
#define MV_CLFLAG_SUBMODEL_BYPASS (1) // Indicates the client engine supports bypassing the MAX_SUBMODEL limit if the cgame module and the server support it, too.
#define MV_CLFLAG_ADVANCED_REMAPS (1 << 1) // Indicates the client engine supports the new advanced shader remaps.

//
// config strings are a general means of communicating variable length strings
// from the server to all connected clients.
Expand Down
5 changes: 0 additions & 5 deletions src/renderer/tr_bsp.cpp
Expand Up @@ -386,11 +386,6 @@ static shader_t *ShaderForShaderNum( int shaderNum, const int *lightmapNum, cons

shader = R_FindShader( dsh->shader, lightmapNum, styles, qtrue );

// if the shader had errors, just use default shader
if ( shader->defaultShader ) {
return tr.defaultShader;
}

return shader;
}

Expand Down
6 changes: 6 additions & 0 deletions src/renderer/tr_init.cpp
Expand Up @@ -191,6 +191,7 @@ cvar_t *r_textureLODBias;
cvar_t *r_saberGlow;
cvar_t *r_environmentMapping;
cvar_t *r_printMissingModels;
cvar_t *r_newRemaps;

#ifndef DEDICATED
PFNGLACTIVETEXTUREARBPROC qglActiveTextureARB;
Expand Down Expand Up @@ -1081,6 +1082,9 @@ void R_Register( void )
r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0);
r_subdivisions = ri.Cvar_Get("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_GLOBAL | CVAR_LATCH);
r_ignoreFastPath = ri.Cvar_Get("r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_GLOBAL | CVAR_LATCH);
r_newRemaps = ri.Cvar_Get("r_newRemaps", "0", CVAR_CHEAT ); // Only used for testing. Classic remaps are supposed to remain fullbright,
// because that is how they have been used by maps and serverside mods for
// more than 20 years. Servers can set a configstring for "mvremap" now.

//
// temporary latched variables that can only change over a restart
Expand Down Expand Up @@ -1537,6 +1541,8 @@ refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
re.AnyLanguage_ReadCharFromString = AnyLanguage_ReadCharFromString;

re.RemapShader = R_RemapShader;
re.RemapShaderAdvanced = R_RemapShaderAdvanced;
re.RemoveAdvancedRemaps = R_RemoveAdvancedRemaps;
re.GetEntityToken = R_GetEntityToken;
re.inPVS = R_inPVS;

Expand Down
12 changes: 11 additions & 1 deletion src/renderer/tr_local.h
Expand Up @@ -492,7 +492,10 @@ Ghoul2 Insert End
// True if this shader has a stage with glow in it (just an optimization).
qboolean hasGlow;

qboolean isAdvancedRemap;

struct shader_s *remappedShader; // current shader this one is remapped too
struct shader_s *remappedShaderAdvanced; // current shader from the advanced remaps this one is remapped to

struct shader_s *next;
} shader_t;
Expand Down Expand Up @@ -1104,6 +1107,9 @@ typedef struct {
shader_t *shaders[MAX_SHADERS];
shader_t *sortedShaders[MAX_SHADERS];

int numAdvancedRemapShaders;
shader_t *advancedRemapShaders[MAX_SHADERS];

int numSkins;
skin_t *skins[MAX_SKINS];

Expand Down Expand Up @@ -1297,6 +1303,7 @@ extern cvar_t *r_textureLODBias;
extern cvar_t *r_saberGlow;
extern cvar_t *r_environmentMapping;
extern cvar_t *r_printMissingModels;
extern cvar_t *r_newRemaps;
//====================================================================

float R_NoiseGet4f( float x, float y, float z, double t );
Expand Down Expand Up @@ -1453,13 +1460,16 @@ qhandle_t RE_RegisterShader( const char *name );
qhandle_t RE_RegisterShaderNoMip( const char *name );
qhandle_t RE_RegisterShaderFromImage(const char *name, int *lightmapIndex, byte *styles, image_t *image, qboolean mipRawImage);

shader_t *R_FindShader( const char *name, const int *lightmapIndex, const byte *styles, qboolean mipRawImage );
shader_t *R_FindShader( const char *name, const int *lightmapIndex, const byte *styles, qboolean mipRawImage, qboolean isAdvancedRemap = qfalse );
shader_t *R_FindAdvancedRemapShader( const char *name, const int *lightmapIndex, const byte *styles, qboolean mipRawImage );
shader_t *R_GetShaderByHandle( qhandle_t hShader );
// shader_t *R_GetShaderByState( int index, int *cycleTime );
shader_t *R_FindShaderByName( const char *name );
void R_InitShaders( void );
void R_ShaderList_f( void );
void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset);
void R_RemapShaderAdvanced(const char *shaderName, const char *newShaderName, int timeOffset, shaderRemapLightmapType_t lightmapMode, shaderRemapStyleType_t styleMode);
void R_RemoveAdvancedRemaps( void );

/*
====================================================================
Expand Down

0 comments on commit 7dc8400

Please sign in to comment.