From 7e2aad096d093bec505740814ca793dd944d2e76 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 10 Oct 2025 03:25:44 +0200 Subject: [PATCH 1/3] tr_shader: delayed stage texture loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also make sure to invalidate stages with a missing colormap that is of different kind (diffusemap…). --- src/engine/renderer/tr_shader.cpp | 120 ++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 647eae66c6..170d7cf40e 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -87,6 +87,14 @@ static Cvar::Cvar r_portalDefaultRange( Cvar::Cvar r_depthShaders( "r_depthShaders", "use depth pre-pass shaders", Cvar::CHEAT, true); +struct delayedStageTexture_t { + bool active; + stageType_t type; + char path[ MAX_QPATH ]; +}; + +delayedStageTexture_t delayedStageTextures[ MAX_TEXTURE_BUNDLES ]; + /* ================ return a hash value for the filename @@ -1591,6 +1599,14 @@ static bool ParseClampType( const char *token, wrapType_t *clamp ) return true; } +static void DelayStageTexture( const char* texturePath, const stageType_t stageType, const int bundleIndex ) +{ + Log::Debug("Delaying the loading of stage texture %s", texturePath ); + strncpy( delayedStageTextures[ bundleIndex ].path, texturePath, MAX_QPATH - 1 ); + delayedStageTextures[ bundleIndex ].type = stageType; + delayedStageTextures[ bundleIndex ].active = true; +} + /* =================== ParseDifuseMap @@ -1603,7 +1619,16 @@ static void ParseDiffuseMap( shaderStage_t *stage, const char **text, const int if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_DIFFUSEMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_DIFFUSEMAP; + + if ( bundleIndex == TB_DIFFUSEMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -1632,7 +1657,16 @@ static void ParseNormalMap( shaderStage_t *stage, const char **text, const int b if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_NORMALMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_NORMALMAP; + + if ( bundleIndex == TB_NORMALMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -1697,7 +1731,16 @@ static void ParseHeightMap( shaderStage_t *stage, const char **text, const int b if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_HEIGHTMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_HEIGHTMAP; + + if ( bundleIndex == TB_HEIGHTMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -1709,7 +1752,16 @@ static void ParseSpecularMap( shaderStage_t *stage, const char **text, const int if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_SPECULARMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_SPECULARMAP; + + if ( bundleIndex == TB_SPECULARMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -1733,7 +1785,16 @@ static void ParsePhysicalMap( shaderStage_t *stage, const char **text, const int if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_PHYSICALMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_PHYSICALMAP; + + if ( bundleIndex == TB_PHYSICALMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -1745,7 +1806,16 @@ static void ParseGlowMap( shaderStage_t *stage, const char **text, const int bun if ( ParseMap( text, buffer, sizeof( buffer ) ) ) { - LoadMap( stage, buffer, stageType_t::ST_GLOWMAP, bundleIndex ); + stageType_t stageType = stageType_t::ST_GLOWMAP; + + if ( bundleIndex == TB_GLOWMAP ) + { + DelayStageTexture( buffer, stageType, bundleIndex ); + } + else + { + LoadMap( stage, buffer, stageType, bundleIndex ); + } } } @@ -2010,6 +2080,8 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) char buffer[ 1024 ] = ""; bool loadMap = false; + memset( delayedStageTextures, 0, sizeof( delayedStageTextures ) ); + while ( true ) { token = COM_ParseExt2( text, true ); @@ -3273,8 +3345,40 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) return true; } - // load image - if ( loadMap && !LoadMap( stage, buffer, stage->type ) ) + /* If there is more than one colormap, it's a mistake anyway, + so we don't care about the possibility it overwrites one. */ + if ( loadMap ) + { + DelayStageTexture( buffer, stage->type, TB_COLORMAP ); + } + + /* Make sure we report for the successful loading of all kind + of colormaps like diffusemap… */ + loadMap = delayedStageTextures[ TB_COLORMAP ].active; + + for ( int bundleIndex = 0; bundleIndex < MAX_TEXTURE_BUNDLES; bundleIndex++ ) + { + auto& delayedStageTexture = delayedStageTextures[ bundleIndex ]; + + if ( !delayedStageTexture.active ) + { + continue; + } + + Log::Debug("Loading delayed stage texture %s", delayedStageTexture.path ); + + if ( !LoadMap( stage, delayedStageTexture.path, delayedStageTexture.type, bundleIndex ) ) + { + Log::Warn("Failed to load delayed stage texture from shader '%s': %s", + shader.name, delayedStageTexture.path ); + + // Remember the texture wasn't sucessfully loaded. + delayedStageTexture.active = false; + } + } + + // Check if the colormap loaded, if it is required for it to be loaded. + if ( loadMap && !delayedStageTextures[ TB_COLORMAP ].active ) { return false; } From 790980b79d8c5a0ea5bf83a1e6b9cf4b514d7f0a Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 13 Oct 2025 04:46:47 +0200 Subject: [PATCH 2/3] tr_shader: delayed animation texture loading Also make sure to not attempt to load a colormap after an animMap. --- src/engine/renderer/tr_shader.cpp | 94 +++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 170d7cf40e..d6e4f158da 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -95,6 +95,13 @@ struct delayedStageTexture_t { delayedStageTexture_t delayedStageTextures[ MAX_TEXTURE_BUNDLES ]; +struct delayedAnimationTexture_t { + bool active; + char path[ MAX_QPATH ]; +}; + +delayedAnimationTexture_t delayedAnimationTextures[ MAX_IMAGE_ANIMATIONS ]; + /* ================ return a hash value for the filename @@ -1607,6 +1614,13 @@ static void DelayStageTexture( const char* texturePath, const stageType_t stageT delayedStageTextures[ bundleIndex ].active = true; } +static void DelayAnimationTexture( const char* texturePath, const size_t animationIndex ) +{ + Log::Debug("Delaying the loading of animation texture %s", texturePath ); + strncpy( delayedAnimationTextures[ animationIndex ].path, texturePath, MAX_QPATH - 1 ); + delayedAnimationTextures[ animationIndex ].active = true; +} + /* =================== ParseDifuseMap @@ -2079,8 +2093,10 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) filterType_t filterType; char buffer[ 1024 ] = ""; bool loadMap = false; + bool loadAnimMap = false; memset( delayedStageTextures, 0, sizeof( delayedStageTextures ) ); + memset( delayedAnimationTextures, 0, sizeof( delayedAnimationTextures ) ); while ( true ) { @@ -2294,11 +2310,11 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) stage->bundle[ 0 ].imageAnimationSpeed = atof( token ); - // parse up to MAX_IMAGE_ANIMATIONS animations - while ( true ) - { - int num; + loadAnimMap = true; + // Parse up to MAX_IMAGE_ANIMATIONS animation frames, skip extraneous ones. + for ( size_t animationIndex = 0; ; animationIndex++ ) + { token = COM_ParseExt2( text, false ); if ( !token[ 0 ] ) @@ -2306,32 +2322,9 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) break; } - num = stage->bundle[ 0 ].numImages; - - if ( num < MAX_IMAGE_ANIMATIONS ) + if ( animationIndex < MAX_IMAGE_ANIMATIONS ) { - imageParams_t imageParams = {}; - imageParams.bits = IF_NONE; - imageParams.filterType = filterType_t::FT_DEFAULT; - imageParams.wrapType = wrapTypeEnum_t::WT_REPEAT; - imageParams.minDimension = shader.imageMinDimension; - imageParams.maxDimension = shader.imageMaxDimension; - - if ( tr.worldLinearizeTexture ) - { - imageParams.bits |= IF_SRGB; - } - - stage->bundle[ 0 ].image[ num ] = R_FindImageFile( token, imageParams ); - - if ( !stage->bundle[ 0 ].image[ num ] ) - { - Log::Warn("R_FindImageFile could not find '%s' in shader '%s'", token, - shader.name ); - return false; - } - - stage->bundle[ 0 ].numImages++; + DelayAnimationTexture( token, animationIndex ); } } } @@ -3345,6 +3338,49 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) return true; } + if ( loadAnimMap ) + { + for ( size_t animationIndex = 0; animationIndex < MAX_IMAGE_ANIMATIONS; animationIndex++ ) + { + auto& delayedAnimationTexture = delayedAnimationTextures[ animationIndex ]; + + if ( delayedAnimationTexture.active ) + { + auto& numImages = stage->bundle[ 0 ].numImages; + + imageParams_t imageParams = {}; + imageParams.bits = IF_NONE; + imageParams.filterType = filterType_t::FT_DEFAULT; + imageParams.wrapType = wrapTypeEnum_t::WT_REPEAT; + imageParams.minDimension = shader.imageMinDimension; + imageParams.maxDimension = shader.imageMaxDimension; + + if ( tr.worldLinearizeTexture ) + { + imageParams.bits |= IF_SRGB; + } + + Log::Debug("Loading delayed animation texture %s", delayedAnimationTexture.path ); + + stage->bundle[ 0 ].image[ numImages ] = + R_FindImageFile( delayedAnimationTexture.path, imageParams ); + + if ( !stage->bundle[ 0 ].image[ numImages ] ) + { + Log::Warn("Failed to load delayed animation texture %d from shader '%s': %s", + animationIndex, shader.name, delayedAnimationTexture.path ); + + return false; + } + + numImages++; + } + } + + // If there is also a colormap it's a mistake, so we can stop now. + return true; + } + /* If there is more than one colormap, it's a mistake anyway, so we don't care about the possibility it overwrites one. */ if ( loadMap ) From f949c64a8b97739b30799b1f637c04c42cb484e3 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 10 Oct 2025 03:35:12 +0200 Subject: [PATCH 3/3] tr_shader: add the linearColorMap, linearSpecularMap and linearRgbGen keywords and raw variants - lienarColorMap, linearSpecularMap and linearRgbGen expects tr.worldLinearizeTexture. They are meant to be used in assets targeting the linear pipeline only. - rawColorMap, rawSpecularMap and rawRgbGen does the same, but there is no colorspace meaning. They are meant to be used when porting legacy assets that may be used in the naive pipeline to be rendered as they were before for historical purposes, while being ported the hacky way for the linear pipeline. --- src/engine/renderer/tr_init.cpp | 2 +- src/engine/renderer/tr_local.h | 17 +++++++++-- src/engine/renderer/tr_shade.cpp | 8 +++--- src/engine/renderer/tr_shader.cpp | 48 ++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index e0cae98d2e..6d52b59b4b 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -1287,7 +1287,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p } static float convertFloatFromSRGB_NOP( float f ) { return f; } - static Color::Color convertColorFromSRGB_NOP( Color::Color c ) { return c; } + Color::Color convertColorFromSRGB_NOP( Color::Color c ) { return c; } /* =============== diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index bc54905438..100f14228c 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -979,6 +979,11 @@ enum ALL = BIT( 3 ) }; + using floatProcessor_t = float(*)(float); + using colorProcessor_t = Color::Color(*)(Color::Color); + + Color::Color convertColorFromSRGB_NOP( Color::Color c ); + struct shaderStage_t { stageType_t type; @@ -998,6 +1003,8 @@ enum stageShaderBinder_t shaderBinder; stageMaterialProcessor_t materialProcessor; + colorProcessor_t convertColorFromSRGB; + textureBundle_t bundle[ MAX_TEXTURE_BUNDLES ]; expression_t ifExp; @@ -1021,6 +1028,7 @@ enum Color::Color32Bit constantColor; // for CGEN_CONST and AGEN_CONST uint32_t stateBits; // GLS_xxxx mask + uint8_t colorspaceBits; // LINEAR_xxxx mask bool isCubeMap; @@ -1272,6 +1280,12 @@ enum GLS_DEFAULT = GLS_DEPTHMASK_TRUE }; + enum { + LINEAR_RGBGEN = ( 1 << 0 ), + LINEAR_COLORMAP = ( 1 << 1 ), + LINEAR_SPECULARMAP = ( 1 << 2 ), + }; + // *INDENT-ON* struct ShaderProgramDescriptor; @@ -2397,9 +2411,6 @@ enum int h; }; - using floatProcessor_t = float(*)(float); - using colorProcessor_t = Color::Color(*)(Color::Color); - /* ** trGlobals_t ** diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index 713934f9d1..7d1a1d5c15 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -1723,7 +1723,7 @@ void Tess_ComputeColor( shaderStage_t *pStage ) { tess.svars.color = pStage->constantColor; tess.svars.color.Clamp(); - tess.svars.color = tr.convertColorFromSRGB( tess.svars.color ); + tess.svars.color = pStage->convertColorFromSRGB( tess.svars.color ); break; } @@ -1733,7 +1733,7 @@ void Tess_ComputeColor( shaderStage_t *pStage ) { tess.svars.color = backEnd.currentEntity->e.shaderRGBA; tess.svars.color.Clamp(); - tess.svars.color = tr.convertColorFromSRGB( tess.svars.color ); + tess.svars.color = pStage->convertColorFromSRGB( tess.svars.color ); } else { @@ -1749,7 +1749,7 @@ void Tess_ComputeColor( shaderStage_t *pStage ) { tess.svars.color = backEnd.currentEntity->e.shaderRGBA; tess.svars.color.Clamp(); - tess.svars.color = tr.convertColorFromSRGB( tess.svars.color ); + tess.svars.color = pStage->convertColorFromSRGB( tess.svars.color ); } else { @@ -1782,7 +1782,7 @@ void Tess_ComputeColor( shaderStage_t *pStage ) tess.svars.color = Color::White * glow; tess.svars.color.Clamp(); - tess.svars.color = tr.convertColorFromSRGB( tess.svars.color ); + tess.svars.color = pStage->convertColorFromSRGB( tess.svars.color ); break; } diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index d6e4f158da..0ccf61f096 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -1491,15 +1491,20 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, to use any shader in UI, so we better take care of more than ST_COLORMAP. */ if ( ! ( shader.registerFlags & RSF_2D ) ) { + stage->convertColorFromSRGB = stage->colorspaceBits & LINEAR_RGBGEN ? + convertColorFromSRGB_NOP : stage->convertColorFromSRGB; + switch ( type ) { case stageType_t::ST_COLORMAP: case stageType_t::ST_DIFFUSEMAP: + imageParams.bits |= stage->colorspaceBits & LINEAR_COLORMAP ? 0 : IF_SRGB; + break; case stageType_t::ST_GLOWMAP: case stageType_t::ST_REFLECTIONMAP: case stageType_t::ST_SKYBOXMAP: case stageType_t::ST_SPECULARMAP: - imageParams.bits |= IF_SRGB; + imageParams.bits |= stage->colorspaceBits & LINEAR_SPECULARMAP ? 0 : IF_SRGB; break; default: break; @@ -2095,6 +2100,8 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) bool loadMap = false; bool loadAnimMap = false; + stage->convertColorFromSRGB = convertColorFromSRGB_NOP; + memset( delayedStageTextures, 0, sizeof( delayedStageTextures ) ); memset( delayedAnimationTextures, 0, sizeof( delayedAnimationTextures ) ); @@ -2641,6 +2648,45 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) depthMaskBits = 0; } } + else if ( !Q_stricmp( token, "rawRgbGen" ) ) + { + stage->colorspaceBits |= LINEAR_RGBGEN; + } + else if ( !Q_stricmp( token, "linearRgbGen" ) ) + { + if ( !tr.worldLinearizeTexture ) + { + Log::Warn("Usage of linearRgbGen in naive pipeline, assuming rawRgbGen"); + } + + stage->colorspaceBits |= LINEAR_RGBGEN; + } + else if ( !Q_stricmp( token, "rawColorMap" ) ) + { + stage->colorspaceBits |= LINEAR_COLORMAP; + } + else if ( !Q_stricmp( token, "linearColorMap" ) ) + { + if ( !tr.worldLinearizeTexture ) + { + Log::Warn("Usage of linearColorMap in naive pipeline, assuming rawColorMap"); + } + + stage->colorspaceBits |= LINEAR_COLORMAP; + } + else if ( !Q_stricmp( token, "rawSpecularMap" ) ) + { + stage->colorspaceBits |= LINEAR_SPECULARMAP; + } + else if ( !Q_stricmp( token, "linearSpecularMap" ) ) + { + if ( !tr.worldLinearizeTexture ) + { + Log::Warn("Usage of linearSpecularMap in naive pipeline, assuming rawSpecularMap"); + } + + stage->colorspaceBits |= LINEAR_SPECULARMAP; + } // stage else if ( !Q_stricmp( token, "stage" ) ) {