Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Shader Remaps #160

Merged
merged 10 commits into from Sep 26, 2023
Merged
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 == CS_MV_REMAPS ) {
CL_ShaderStateChanged();
}
}


Expand Down
128 changes: 128 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 @@ -4043,3 +4046,128 @@ 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 = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_MV_REMAPS ];
const char *endPos = curPos + strlen( curPos );

// Check configstring for prefix to avoid misinterpreting configstrings from mods
if ( !curPos || !*curPos || strncmp(curPos, "mvremap:", 8) ) return;

// Handling of these is really ugly. I originally wanted to handle them like the base game/cgame modules do, but
Daggolin marked this conversation as resolved.
Show resolved Hide resolved
// 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
aufau marked this conversation as resolved.
Show resolved Hide resolved
// 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

// Skip prefix
curPos += 8;

int length;

// Parse configstring
while ( curPos && *curPos )
Daggolin marked this conversation as resolved.
Show resolved Hide resolved
{
// Read original shader
if ( curPos >= endPos ) break;
Daggolin marked this conversation as resolved.
Show resolved Hide resolved
length = *(curPos++);
length -= 47;
if ( length < 0 ) 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;
if ( length < 0 ) break;
Q_strncpyz( newShader, curPos, MIN((unsigned int)length+1, sizeof(newShader)) );
curPos += length;

// Read settings
if ( curPos >= endPos ) break;
length = *(curPos++);
length -= 47;
if ( length < 0 ) break;
Q_strncpyz( settings, curPos, MIN((unsigned int)length+1, sizeof(settings)) );
curPos += length;

// Get values from settings
timeOffset = strtok( settings, ";" );
Daggolin marked this conversation as resolved.
Show resolved Hide resolved
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, atoi(timeOffset), lightmapModeValue, styleModeValue );
}
}
1 change: 1 addition & 0 deletions src/client/client.h
Expand Up @@ -501,6 +501,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
11 changes: 11 additions & 0 deletions src/game/bg_public.h
Expand Up @@ -100,6 +100,17 @@ Ghoul2 Insert End
#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS
#endif


// Using highest available configstring for new mv configstring, because mods should add own configstrings between
// CS_STRING_PACKAGES and CS_MAX, making them come from the bottom.
#define CS_MV_REMAPS (MAX_CONFIGSTRINGS-1)
#define CS_MV_MIN (CS_MV_REMAPS)

// Make sure CS_MAX and CS_MV_MIN don't overlap
#if (CS_MAX) > (CS_MV_MIN)
#error overflow: (CS_MAX) > (CS_MV_MIN)
#endif

typedef enum {
G2_MODELPART_HEAD = 10,
G2_MODELPART_WAIST,
Expand Down
5 changes: 5 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 @@ -1079,6 +1080,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 @@ -1518,6 +1522,7 @@ refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
re.AnyLanguage_ReadCharFromString = AnyLanguage_ReadCharFromString;

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

Expand Down
4 changes: 4 additions & 0 deletions src/renderer/tr_local.h
Expand Up @@ -492,6 +492,8 @@ Ghoul2 Insert End
// True if this shader has a stage with glow in it (just an optimization).
qboolean hasGlow;

qboolean advancedRemap;

struct shader_s *remappedShader; // current shader this one is remapped too

struct shader_s *next;
Expand Down Expand Up @@ -1297,6 +1299,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 @@ -1459,6 +1462,7 @@ 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);

/*
====================================================================
Expand Down
17 changes: 17 additions & 0 deletions src/renderer/tr_public.h
Expand Up @@ -5,6 +5,22 @@

#define REF_API_VERSION 8


typedef enum
{
SHADERREMAP_LIGHTMAP_PRESERVE,
SHADERREMAP_LIGHTMAP_NONE,
SHADERREMAP_LIGHTMAP_FULLBRIGHT,
SHADERREMAP_LIGHTMAP_VERTEX,
SHADERREMAP_LIGHTMAP_2D,
} shaderRemapLightmapType_t;

typedef enum
{
SHADERREMAP_STYLE_PRESERVE,
SHADERREMAP_STYLE_DEFAULT,
} shaderRemapStyleType_t;

//
// these are the functions exported by the refresh module
//
Expand Down Expand Up @@ -90,6 +106,7 @@ typedef struct {
unsigned int (*AnyLanguage_ReadCharFromString)( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ );

void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime);
void (*RemapShaderAdvanced)(const char *oldShader, const char *newShader, int offsetTime, shaderRemapLightmapType_t lightmapMode, shaderRemapStyleType_t styleMode);
qboolean (*GetEntityToken)( char *buffer, int size );
qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 );

Expand Down
86 changes: 85 additions & 1 deletion src/renderer/tr_shader.cpp
Expand Up @@ -205,6 +205,11 @@ void R_RemapShader(const char *shaderName, const char *newShaderName, const char
shader_t *sh, *sh2;
qhandle_t h;

if ( r_newRemaps->integer ) {
R_RemapShaderAdvanced( shaderName, newShaderName, (int)(atof(timeOffset)*1000), SHADERREMAP_LIGHTMAP_PRESERVE, SHADERREMAP_STYLE_PRESERVE );
return;
}

sh = R_FindShaderByName( shaderName );
if (sh == NULL || sh == tr.defaultShader) {
h = RE_RegisterShaderLightMap(shaderName, lightmapsNone, stylesDefault);
Expand Down Expand Up @@ -232,10 +237,15 @@ void R_RemapShader(const char *shaderName, const char *newShaderName, const char
hash = generateHashValue(strippedName, FILE_HASH_SIZE);
for (sh = hashTable[hash]; sh; sh = sh->next) {
if (Q_stricmp(sh->name, strippedName) == 0) {
if (sh != sh2) {
if (sh->remappedShader && sh->advancedRemap) {
// Advanced remaps take priority over the old ones
continue;
} else if (sh != sh2) {
Daggolin marked this conversation as resolved.
Show resolved Hide resolved
sh->remappedShader = sh2;
sh->advancedRemap = qfalse;
} else {
sh->remappedShader = NULL;
sh->advancedRemap = qfalse;
}
}
}
Expand All @@ -244,6 +254,80 @@ void R_RemapShader(const char *shaderName, const char *newShaderName, const char
}
}

void R_RemapShaderAdvanced(const char *shaderName, const char *newShaderName, int timeOffset, shaderRemapLightmapType_t lightmapMode, shaderRemapStyleType_t styleMode) {
char strippedName[MAX_QPATH];
int hash;
shader_t *sh, *sh2 = NULL;
qhandle_t h;
int failed = 0;
const int *lightmapIndex = NULL;
const byte *styles = NULL;

// Check if at least one instance of the original exists
sh = R_FindShaderByName( shaderName );
if (sh == NULL || sh == tr.defaultShader || sh->defaultShader) {
h = RE_RegisterShaderLightMap(shaderName, lightmapsNone, stylesDefault);
sh = R_GetShaderByHandle(h);
}
if (sh == NULL || sh == tr.defaultShader || sh->defaultShader) {
ri.Printf( PRINT_WARNING, "WARNING: R_RemapShaderAdvanced: shader %s not found\n", shaderName );
return;
}

// Lightmap
if ( lightmapMode == SHADERREMAP_LIGHTMAP_FULLBRIGHT ) {
lightmapIndex = lightmapsFullBright;
} else if ( lightmapMode == SHADERREMAP_LIGHTMAP_NONE ) {
lightmapIndex = lightmapsNone;
} else if ( lightmapMode == SHADERREMAP_LIGHTMAP_2D ) {
lightmapIndex = lightmaps2d;
} else if ( lightmapMode == SHADERREMAP_LIGHTMAP_VERTEX ) {
lightmapIndex = lightmapsVertex;
}

// Style
if ( styleMode == SHADERREMAP_STYLE_DEFAULT ) {
styles = stylesDefault;
}

if ( lightmapIndex && lightmapMode != SHADERREMAP_LIGHTMAP_VERTEX ) {
// For fullbright, none and 2d we only need one new shader
sh2 = R_FindShader( newShaderName, lightmapIndex, stylesDefault, qtrue );

if ( !sh2 || sh2 == tr.defaultShader || sh2->defaultShader ) {
ri.Printf( PRINT_WARNING, "WARNING: R_RemapShaderAdvanced: new shader %s not found\n", newShaderName );
return;
}
}

// Remap all the shaders with the given name and copy over the lightmap + styles
COM_StripExtension( shaderName, strippedName, sizeof(strippedName) );
hash = generateHashValue(strippedName, FILE_HASH_SIZE);
for (sh = hashTable[hash]; sh; sh = sh->next) {
if (Q_stricmp(sh->name, strippedName) == 0) {
if ( lightmapMode == SHADERREMAP_LIGHTMAP_PRESERVE || lightmapMode == SHADERREMAP_LIGHTMAP_VERTEX ) {
// When preserving lightmaps we need to use the correct lightmap index (+styles)
sh2 = R_FindShader( newShaderName, lightmapIndex ? lightmapIndex : sh->lightmapIndex, styles ? styles : sh->styles, (qboolean)!sh->upload.noMipMaps );
}

if ( !sh2 || sh2 == tr.defaultShader || sh2->defaultShader ) {
failed++;
continue;
}
if ( timeOffset ) sh2->timeOffset = timeOffset * 0.001;

if (sh != sh2) {
sh->remappedShader = sh2;
sh->advancedRemap = qtrue;
} else {
sh->remappedShader = NULL;
sh->advancedRemap = qfalse;
}
}
}
if ( failed ) ri.Printf( PRINT_WARNING, "WARNING: R_RemapShaderAdvanced: new shader %s not found (x%i)\n", newShaderName, failed );
}

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