From ef687eb2018e5f81170e6a80d41949faf2a1a1ac Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 10 Oct 2025 03:25:44 +0200 Subject: [PATCH 1/4] 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 aaa7a008ead8c75265683c032416af77740e7408 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 13 Oct 2025 04:46:47 +0200 Subject: [PATCH 2/4] 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 c90124a66c7bb73d2cdb5e66642fc2ad70f63962 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 10 Oct 2025 03:35:12 +0200 Subject: [PATCH 3/4] tr_shader: add the naiveBlend keyword --- src/engine/renderer/tr_init.cpp | 2 +- src/engine/renderer/tr_local.h | 12 +++++++++--- src/engine/renderer/tr_shade.cpp | 8 ++++---- src/engine/renderer/tr_shader.cpp | 19 ++++++++++++++++++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index e2db9fe8f9..7ad3852fd5 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -1283,7 +1283,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 a6cfc3d47e..68c1a11bc0 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; @@ -1269,6 +1276,8 @@ enum | GLS_BLUEMASK_FALSE | GLS_ALPHAMASK_FALSE, + GLS_NAIVEBLEND = ( 1 << 30 ), + GLS_DEFAULT = GLS_DEPTHMASK_TRUE }; @@ -2397,9 +2406,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 19c481d928..973e6c92dd 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -1698,7 +1698,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; } @@ -1708,7 +1708,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 { @@ -1724,7 +1724,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 { @@ -1757,7 +1757,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..fec1f71c17 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -1495,6 +1495,16 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, { case stageType_t::ST_COLORMAP: case stageType_t::ST_DIFFUSEMAP: + { + bool naiveBlend = stage->stateBits & GLS_NAIVEBLEND; + + if ( !naiveBlend ) + { + imageParams.bits |= IF_SRGB; + stage->convertColorFromSRGB = tr.convertColorFromSRGB; + } + } + break; case stageType_t::ST_GLOWMAP: case stageType_t::ST_REFLECTIONMAP: case stageType_t::ST_SKYBOXMAP: @@ -2088,6 +2098,7 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) const char *token; int colorMaskBits = 0; int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0, polyModeBits = 0; + int naiveBlendBits = 0; bool depthMaskExplicit = false; int imageBits = 0; filterType_t filterType; @@ -2095,6 +2106,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 +2654,10 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) depthMaskBits = 0; } } + else if ( !Q_stricmp( token, "naiveBlend" ) ) + { + naiveBlendBits |= GLS_NAIVEBLEND; + } // stage else if ( !Q_stricmp( token, "stage" ) ) { @@ -3329,7 +3346,7 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) } // compute state bits - stage->stateBits = colorMaskBits | depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits | polyModeBits; + stage->stateBits = colorMaskBits | depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits | polyModeBits | naiveBlendBits; // Do not load heatHaze maps when r_heatHaze is disabled. if ( stage->type == stageType_t::ST_HEATHAZEMAP && !r_heatHaze->integer ) From ab0213f115eb3683be767260c913a19692ca5c9f Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 15 Oct 2025 13:25:48 +0200 Subject: [PATCH 4/4] wip: rename naiveBlend to naiveColors --- src/engine/renderer/tr_local.h | 2 +- src/engine/renderer/tr_shader.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 68c1a11bc0..f462d621cb 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1276,7 +1276,7 @@ enum | GLS_BLUEMASK_FALSE | GLS_ALPHAMASK_FALSE, - GLS_NAIVEBLEND = ( 1 << 30 ), + GLS_NAIVECOLORS = ( 1 << 30 ), GLS_DEFAULT = GLS_DEPTHMASK_TRUE }; diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index fec1f71c17..26bd206ab4 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -1496,9 +1496,9 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, case stageType_t::ST_COLORMAP: case stageType_t::ST_DIFFUSEMAP: { - bool naiveBlend = stage->stateBits & GLS_NAIVEBLEND; + bool naiveColors = stage->stateBits & GLS_NAIVECOLORS; - if ( !naiveBlend ) + if ( !naiveColors ) { imageParams.bits |= IF_SRGB; stage->convertColorFromSRGB = tr.convertColorFromSRGB; @@ -2098,7 +2098,7 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) const char *token; int colorMaskBits = 0; int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0, polyModeBits = 0; - int naiveBlendBits = 0; + int naiveBits = 0; bool depthMaskExplicit = false; int imageBits = 0; filterType_t filterType; @@ -2654,9 +2654,9 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) depthMaskBits = 0; } } - else if ( !Q_stricmp( token, "naiveBlend" ) ) + else if ( !Q_stricmp( token, "naiveColors" ) ) { - naiveBlendBits |= GLS_NAIVEBLEND; + naiveBits |= GLS_NAIVECOLORS; } // stage else if ( !Q_stricmp( token, "stage" ) ) @@ -3346,7 +3346,7 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) } // compute state bits - stage->stateBits = colorMaskBits | depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits | polyModeBits | naiveBlendBits; + stage->stateBits = colorMaskBits | depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits | polyModeBits | naiveBits; // Do not load heatHaze maps when r_heatHaze is disabled. if ( stage->type == stageType_t::ST_HEATHAZEMAP && !r_heatHaze->integer )