Skip to content

Allow registering shaders multiple times with different RSF_ flags #1700

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

Merged
merged 3 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/engine/renderer/tr_bsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ static shader_t* ShaderForShaderNum( int shaderNum ) {

dshader_t* dsh = &s_worldData.shaders[shaderNum];

shader_t* shader = R_FindShader( dsh->shader, shaderType_t::SHADER_3D, RSF_DEFAULT );
shader_t* shader = R_FindShader( dsh->shader, RSF_3D );

// If the shader had errors, just use default shader
if ( shader->defaultShader ) {
Expand Down Expand Up @@ -3271,7 +3271,9 @@ static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump )
}

// get information from the shader for fog parameters
shader = R_FindShader( fogs->shader, shaderType_t::SHADER_3D, RSF_DEFAULT );
// it says RSF_3D but if there is no shader text found it should probably just error instead
// of trying to create an implicit shader from an image...
shader = R_FindShader( fogs->shader, RSF_3D );

out->fogParms = shader->fogParms;

Expand Down
14 changes: 6 additions & 8 deletions src/engine/renderer/tr_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,10 @@ enum class ssaoMode {
struct Material;
struct MaterialSurface;

// [implicit only] enable lightmapping, front-side culling, disable (non-BSP) vertex colors, disable blending
// TODO(0.56): move to the public RegisterShaderFlags_t interface
#define RSF_3D ( BIT( 30 ) )

using stageRenderer_t = void(*)(shaderStage_t *);
using stageShaderBuildMarker_t = void(*)(const shaderStage_t*);
using surfaceDataUpdater_t = void(*)(uint32_t*, shaderStage_t*, bool, bool, bool);
Expand Down Expand Up @@ -1205,16 +1209,10 @@ enum class ssaoMode {
float depthForOpaque;
};

enum class shaderType_t
{
SHADER_2D, // surface material: shader is for 2D rendering (like GUI elements)
SHADER_3D,
};

struct shader_t
{
char name[ MAX_QPATH ]; // game path, including extension
shaderType_t type;
int registerFlags; // RSF_

int index; // this shader == tr.shaders[index]
int sortedIndex; // this shader == tr.sortedShaders[sortedIndex]
Expand Down Expand Up @@ -3103,7 +3101,7 @@ inline bool checkGLErrors()
qhandle_t RE_RegisterShader( const char *name, int flags );
qhandle_t RE_RegisterShaderFromImage( const char *name, image_t *image );

shader_t *R_FindShader( const char *name, shaderType_t type, int flags );
shader_t *R_FindShader( const char *name, int flags );
shader_t *R_GetShaderByHandle( qhandle_t hShader );
shader_t *R_FindShaderByName( const char *name );
const char *RE_GetShaderNameFromHandle( qhandle_t shader );
Expand Down
4 changes: 2 additions & 2 deletions src/engine/renderer/tr_model_iqm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,8 @@ bool R_LoadIQModel( model_t *mod, const void *buffer, int filesize,
surface->name = nullptr;
}

surface->shader = R_FindShader( ( char* )IQMPtr(header, header->ofs_text + mesh->material),
shaderType_t::SHADER_3D, RSF_DEFAULT );
surface->shader = R_FindShader(
( char* )IQMPtr(header, header->ofs_text + mesh->material), RSF_3D );
if( surface->shader->defaultShader )
surface->shader = tr.defaultShader;
surface->data = IQModel;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/renderer/tr_model_md3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ bool R_LoadMD3( model_t *mod, int lod, const void *buffer, const char *modName )

// only consider the first shader
md3Shader = ( md3Shader_t * )( ( byte * ) md3Surf + md3Surf->ofsShaders );
surf->shader = R_FindShader( md3Shader->name, shaderType_t::SHADER_3D, RSF_DEFAULT );
surf->shader = R_FindShader( md3Shader->name, RSF_3D );

// swap all the triangles
surf->numTriangles = md3Surf->numTriangles;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/renderer/tr_model_md5.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ bool R_LoadMD5( model_t *mod, const char *buffer, const char *modName )
// lowercase the surface name so skin compares are faster

// register the shaders
sh = R_FindShader( surf->shader, shaderType_t::SHADER_3D, RSF_DEFAULT );
sh = R_FindShader( surf->shader, RSF_3D );

if ( sh->defaultShader )
{
Expand Down
134 changes: 57 additions & 77 deletions src/engine/renderer/tr_shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5833,7 +5833,7 @@ static shader_t *FinishShader()
numStages > 0 &&
(stages[0].stateBits & GLS_DEPTHMASK_TRUE) &&
!(stages[0].stateBits & GLS_DEPTHFUNC_EQUAL) &&
!(shader.type == shaderType_t::SHADER_2D) &&
!( shader.registerFlags & RSF_2D ) &&
!shader.polygonOffset ) {
// keep only the first stage
stages[1].active = false;
Expand Down Expand Up @@ -5920,7 +5920,7 @@ static shader_t *FinishShader()
if ( ret->altShader[ i ].name )
{
// flags were previously stashed in altShader[0].index
shader_t *sh = R_FindShader( ret->altShader[ i ].name, ret->type, (RegisterShaderFlags_t)ret->altShader[ 0 ].index );
shader_t *sh = R_FindShader( ret->altShader[ i ].name, ret->registerFlags );

ret->altShader[ i ].index = sh->defaultShader ? 0 : sh->index;
}
Expand Down Expand Up @@ -6018,17 +6018,16 @@ Will always return a valid shader, but it might be the
default shader if the real one can't be found.

In the interest of not requiring an explicit shader text entry to
be defined for every single image used in the game, two default
shader behaviors can be auto-created for any image that does not
have an explicit shader:

If type == SHADER_2D, then the image will default to using vertex colors.

If type == SHADER_3D, then the renderer will choose appropriate precomputed
be defined for every single image used in the game, shaders
can be auto-created for any image that does not
have an explicit shader. RSF_ flags like RSF_2D and RSF_3D
can influence the behaviors of these implicit shaders. For example
among other effects, RSF_3D will cause an implicit shader to use the
appropriate precomputed
lighting: lightmap, (precomputed) vertex or light grid.
===============
*/
shader_t *R_FindShader( const char *name, shaderType_t type, int flags )
shader_t *R_FindShader( const char *name, int flags )
{
char strippedName[ MAX_QPATH ];
char fileName[ MAX_QPATH ];
Expand All @@ -6042,21 +6041,30 @@ shader_t *R_FindShader( const char *name, shaderType_t type, int flags )
return tr.defaultShader;
}

// TODO(0.56): RSF_DEFAULT should be 0!
flags &= ~RSF_DEFAULT;

COM_StripExtension3( name, strippedName, sizeof( strippedName ) );

hash = generateHashValue( strippedName, FILE_HASH_SIZE );

// see if the shader is already loaded
for ( sh = shaderHashTable[ hash ]; sh; sh = sh->next )
{
// NOTE: if there was no shader or image available with the name strippedName
// then a default shader is created with type == SHADER_3D, so we
// have to check all default shaders otherwise for every call to R_FindShader
// with that same strippedName a new default shader is created.
if ( ( sh->type == type || sh->defaultShader ) && !Q_stricmp( sh->name, strippedName ) )
if ( !Q_stricmp( sh->name, strippedName ) )
{
// match found
return sh;
// NOTE: if there was no shader or image available with the name strippedName
// then a default shader is created, so we
// have to check all default shaders otherwise for every call to R_FindShader
// with that same strippedName a new default shader is created.
if ( sh->registerFlags == flags || sh->defaultShader )
{
// match found
return sh;
}

Log::Verbose( "shader %s registered with varying flags: previously with 0x%X, now with 0x%X",
strippedName, sh->registerFlags, flags );
}
}

Expand All @@ -6069,7 +6077,7 @@ shader_t *R_FindShader( const char *name, shaderType_t type, int flags )
ClearGlobalShader();

Q_strncpyz( shader.name, strippedName, sizeof( shader.name ) );
shader.type = type;
shader.registerFlags = flags;

for ( i = 0; i < MAX_SHADER_STAGES; i++ )
{
Expand All @@ -6079,13 +6087,14 @@ shader_t *R_FindShader( const char *name, shaderType_t type, int flags )
// ydnar: default to no implicit mappings
implicitMap[ 0 ] = '\0';
implicitStateBits = GLS_DEFAULT;
if( shader.type == shaderType_t::SHADER_2D )

if ( shader.registerFlags & RSF_3D )
{
implicitCullType = CT_TWO_SIDED;
implicitCullType = CT_FRONT_SIDED;
}
else
{
implicitCullType = CT_FRONT_SIDED;
implicitCullType = CT_TWO_SIDED;
}

if ( flags & RSF_NOMIP )
Expand Down Expand Up @@ -6191,32 +6200,22 @@ shader_t *R_FindShader( const char *name, shaderType_t type, int flags )
shader.cullType = implicitCullType;
}

// create the default shading commands
switch ( shader.type )
if ( flags & RSF_3D )
{
case shaderType_t::SHADER_2D:
{
// GUI elements
stages[ 0 ].bundle[ 0 ].image[ 0 ] = image;
stages[ 0 ].active = true;
stages[ 0 ].rgbGen = colorGen_t::CGEN_VERTEX;
stages[ 0 ].alphaGen = alphaGen_t::AGEN_VERTEX;
stages[ 0 ].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
break;
}

case shaderType_t::SHADER_3D:
{
stages[ 0 ].type = stageType_t::ST_COLLAPSE_DIFFUSEMAP;
stages[ 0 ].bundle[ 0 ].image[ 0 ] = image;
stages[ 0 ].active = true;
stages[ 0 ].rgbGen = colorGen_t::CGEN_IDENTITY;
stages[ 0 ].stateBits = implicitStateBits;
break;
}

default:
break;
stages[ 0 ].type = stageType_t::ST_COLLAPSE_DIFFUSEMAP;
stages[ 0 ].bundle[ 0 ].image[ 0 ] = image;
stages[ 0 ].active = true;
stages[ 0 ].rgbGen = colorGen_t::CGEN_IDENTITY;
stages[ 0 ].stateBits = implicitStateBits;
}
else
{
// preset appropriate for GUI elements
stages[ 0 ].bundle[ 0 ].image[ 0 ] = image;
stages[ 0 ].active = true;
stages[ 0 ].rgbGen = colorGen_t::CGEN_VERTEX;
stages[ 0 ].alphaGen = alphaGen_t::AGEN_VERTEX;
stages[ 0 ].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
}

return FinishShader();
Expand All @@ -6232,7 +6231,6 @@ qhandle_t RE_RegisterShaderFromImage( const char *name, image_t *image )
ClearGlobalShader();

Q_strncpyz( shader.name, name, sizeof( shader.name ) );
shader.type = shaderType_t::SHADER_2D;
shader.cullType = CT_TWO_SIDED;

for ( int i = 0; i < MAX_SHADER_STAGES; i++ )
Expand All @@ -6258,9 +6256,6 @@ RE_RegisterShader

This is the exported shader entry point for the rest of the system
It will always return an index that will be valid.

This should really only be used for explicit shaders, because there is no
way to ask for different implicit lighting modes (vertex, lightmap, etc)
====================
*/
qhandle_t RE_RegisterShader( const char *name, int flags )
Expand All @@ -6272,7 +6267,7 @@ qhandle_t RE_RegisterShader( const char *name, int flags )
return 0;
}

sh = R_FindShader( name, shaderType_t::SHADER_2D, flags );
sh = R_FindShader( name, flags );

// we want to return 0 if the shader failed to
// load for some reason, but R_FindShader should
Expand Down Expand Up @@ -6326,11 +6321,6 @@ class ListShadersCmd : public Cmd::StaticCmd

void Run( const Cmd::Args &args ) const override
{
static const std::unordered_map<shaderType_t, std::string> shaderTypeName = {
{ shaderType_t::SHADER_2D, "2D" },
{ shaderType_t::SHADER_3D, "3D" },
};

static const std::unordered_map<shaderSort_t, std::string> shaderSortName = {
{ shaderSort_t::SS_BAD, "BAD" },
{ shaderSort_t::SS_PORTAL, "PORTAL" },
Expand Down Expand Up @@ -6384,7 +6374,7 @@ class ListShadersCmd : public Cmd::StaticCmd

// Header names
std::string num = "num";
std::string shaderType = "shaderType";
std::string regFlags = "regFlags";
std::string shaderSort = "shaderSort";
std::string stageType = "stageType";
std::string stageNumber = "stageNumber";
Expand All @@ -6395,15 +6385,11 @@ class ListShadersCmd : public Cmd::StaticCmd

// Header number sizes
numLen = std::max( numLen, num.length() );
size_t shaderTypeLen = shaderType.length();
size_t regFlagsLen = regFlags.length();
size_t shaderSortLen = shaderSort.length();
size_t stageTypeLen = stageType.length();

// Value size
for ( const auto& kv : shaderTypeName )
{
shaderTypeLen = std::max( shaderTypeLen, kv.second.length() );
}

for ( const auto& kv : shaderSortName )
{
Expand All @@ -6421,7 +6407,7 @@ class ListShadersCmd : public Cmd::StaticCmd
// Print header
lineStream << std::left;
lineStream << std::setw(numLen) << num << separator;
lineStream << std::setw(shaderTypeLen) << shaderType << separator;
lineStream << std::setw(regFlagsLen) << regFlags << separator;
lineStream << std::setw(shaderSortLen) << shaderSort << separator;
lineStream << std::setw(stageTypeLen) << stageType << separator;
lineStream << stageNumber << ":" << shaderName;
Expand All @@ -6445,15 +6431,13 @@ class ListShadersCmd : public Cmd::StaticCmd
continue;
}

if ( !shaderTypeName.count( shader->type ) )
{
Log::Debug( "Undocumented shader type %i for shader %s",
Util::ordinal( shader->type ), shader->name );
}
else
{
shaderType = shaderTypeName.at( shader->type );
}
regFlags = {
shader->registerFlags & RSF_2D ? '2' : '_',
shader->registerFlags & RSF_NOMIP ? 'N' : '_',
shader->registerFlags & RSF_FITSCREEN ? 'F' : '_',
shader->registerFlags & RSF_SPRITE ? 'S' : '_',
shader->registerFlags & RSF_3D ? '3' : '_',
};

if ( !shaderSortName.count( (shaderSort_t) shader->sort ) )
{
Expand All @@ -6475,7 +6459,6 @@ class ListShadersCmd : public Cmd::StaticCmd

lineStream << std::left;
lineStream << std::setw(numLen) << i << separator;
lineStream << std::setw(shaderTypeLen) << shaderType << separator;
lineStream << std::setw(shaderSortLen) << shaderSort << separator;
lineStream << std::setw(stageTypeLen) << stageType << separator;
lineStream << "-:" << shaderName;
Expand Down Expand Up @@ -6507,7 +6490,7 @@ class ListShadersCmd : public Cmd::StaticCmd

lineStream << std::left;
lineStream << std::setw(numLen) << i << separator;
lineStream << std::setw(shaderTypeLen) << shaderType << separator;
lineStream << std::setw(regFlagsLen) << regFlags << separator;
lineStream << std::setw(shaderSortLen) << shaderSort << separator;
lineStream << std::setw(stageTypeLen) << stageType << separator;
lineStream << j << ":" << shaderName;
Expand Down Expand Up @@ -6800,7 +6783,6 @@ static void CreateInternalShaders()

Q_strncpyz( shader.name, "<default>", sizeof( shader.name ) );

shader.type = shaderType_t::SHADER_3D;
shader.noFog = true;
shader.fogShader = nullptr;
stages[ 0 ].type = stageType_t::ST_DIFFUSEMAP;
Expand All @@ -6811,7 +6793,6 @@ static void CreateInternalShaders()

Q_strncpyz( shader.name, "<fogEqual>", sizeof( shader.name ) );

shader.type = shaderType_t::SHADER_3D;
shader.sort = Util::ordinal( shaderSort_t::SS_FOG );
stages[0].type = stageType_t::ST_FOGMAP;
for ( int i = 0; i < 5; i++ ) {
Expand All @@ -6823,7 +6804,6 @@ static void CreateInternalShaders()

Q_strncpyz( shader.name, "<fogLE>", sizeof( shader.name ) );

shader.type = shaderType_t::SHADER_3D;
shader.sort = Util::ordinal( shaderSort_t::SS_FOG );
stages[0].type = stageType_t::ST_FOGMAP;
for ( int i = 0; i < 5; i++ ) {
Expand Down
2 changes: 1 addition & 1 deletion src/engine/renderer/tr_skin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ qhandle_t RE_RegisterSkin( const char *name )
Q_strncpyz( surf->name, surfName, sizeof( surf->name ) );

// RB: bspSurface_t does not have ::hash yet
surf->shader = R_FindShader( token, shaderType_t::SHADER_3D, RSF_DEFAULT );
surf->shader = R_FindShader( token, RSF_3D );
skin->numSurfaces++;
}

Expand Down
Loading