From 69a11b61d11dfe26bb8e6490efb4a8ad1c24d02f Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 8 Sep 2025 21:55:15 +0200 Subject: [PATCH 01/11] tr_backend: fix a comment --- src/engine/renderer/tr_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 3c66a2d249..2c3f2aeca4 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -796,7 +796,7 @@ static GLint GL_ToSRGB( GLint internalFormat, bool isSRGB ) return GL_SRGB8; case GL_RGBA8: return GL_SRGB8_ALPHA8; -#if 0 // Internal formats, should not be used directly. +#if 0 // Not used in the codebase. case GL_COMPRESSED_RGB: return GL_COMPRESSED_SRGB; case GL_COMPRESSED_RGBA: From e4efffb4d9b6e415a013df156b52100c3101e21b Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 8 Sep 2025 17:07:29 +0200 Subject: [PATCH 02/11] tr_image: rework the alpha detection --- src/engine/renderer/tr_image.cpp | 51 +++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 3d87ecf326..09eaec5b37 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1050,30 +1050,53 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int } else { - // scan the texture for each channel's max values - // and verify if the alpha channel is being used or not + // lightmap does not have alpha channel + if ( image->bits & IF_LIGHTMAP ) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGBA8; + } + } - c = image->width * image->height; - scan = dataArray[0]; + // Detect formats. + if ( dataArray ) + { + if ( internalFormat == GL_RGBA8 ) + { + /* Scan the texture for alpha channel's max values + and verify if the alpha channel is being used or not. */ - // lightmap does not have alpha channel + internalFormat = GL_RGB8; - // normalmap may have the heightmap in the alpha channel - // opaque alpha channel means no displacement, so we can enable - // alpha channel everytime it is used, even for normalmap + c = image->width * image->height; - internalFormat = GL_RGB8; + /* A normalmap may have the heightmap in the alpha channel, + an opaque alpha channel means no displacement, so we can enable + the alpha channel everytime it is used, even for normalmap. */ - if ( !( image->bits & IF_LIGHTMAP ) ) - { - for ( i = 0; i < c; i++ ) + for ( int l = 0; l < numLayers; l++ ) { - if ( scan[ i * 4 + 3 ] != 255 ) + scan = dataArray[ l ]; + + for ( i = 0; i < c * 4; i += 4 ) + { + if ( scan[ i + 3 ] != 255 ) + { + internalFormat = GL_RGBA8; + break; + } + } + + if ( internalFormat == GL_RGBA8 ) { - internalFormat = GL_RGBA8; break; } } + + internalFormat = hasAlpha ? GL_RGBA8 : GL_RGB8; } } From 497868c37597f1e044102fb5b9646e8b8398b2e0 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 6 Aug 2025 08:13:39 +0200 Subject: [PATCH 03/11] renderer: implement and detect RED and RG images --- src/engine/renderer/GLUtils.h | 2 + src/engine/renderer/tr_backend.cpp | 13 +++-- src/engine/renderer/tr_image.cpp | 82 +++++++++++++++++++++++++++++- src/engine/sys/sdl_glimp.cpp | 18 +++++-- 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/engine/renderer/GLUtils.h b/src/engine/renderer/GLUtils.h index 7c5645a99b..d0aaaccf4b 100644 --- a/src/engine/renderer/GLUtils.h +++ b/src/engine/renderer/GLUtils.h @@ -128,6 +128,8 @@ struct GLConfig bool gpuShader4Available; bool gpuShader5Available; bool textureGatherAvailable; + bool textureSrgbR8Available; + bool textureSrgbRG8Available; int maxDrawBuffers; float maxTextureAnisotropy; diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 2c3f2aeca4..cd1d234aa3 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -782,12 +782,17 @@ static GLint GL_ToSRGB( GLint internalFormat, bool isSRGB ) { switch ( format ) { -#if 0 // Not used in the code base. - /* EXT_texture_sRGB_R8 extension. - See: https://github.com/KhronosGroup/OpenGL-Registry/blob/main/extensions/EXT/EXT_texture_sRGB_R8.txt */ case GL_RED: + case GL_R8: + /* EXT_texture_sRGB_R8 extension. + See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_sRGB_R8.txt */ + ASSERT( glConfig.textureSrgbR8Available ); return GL_SR8_EXT; -#endif + case GL_RG8: + /* EXT_texture_sRGB_RG8 extension. + See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_sRGB_RG8.txt */ + ASSERT( glConfig.textureSrgbRG8Available ); + return GL_SRG8_EXT; case GL_RGB: return GL_SRGB; case GL_RGBA: diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 09eaec5b37..232c76a934 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -164,6 +164,7 @@ class ListImagesCmd : public Cmd::StaticCmd internal component resolutions of its own choosing, referred to as the effective internal format." Use 4 bytes as an estimate: */ { GL_RGBA, { "RGBA", 4 } }, + { GL_RED, { "RED", 1 } }, { GL_RGB8, { "RGB8", 3 } }, { GL_RGBA8, { "RGBA8", 4 } }, @@ -176,10 +177,12 @@ class ListImagesCmd : public Cmd::StaticCmd { GL_RGBA32UI, { "RGBA32UI", 16 } }, { GL_ALPHA16F_ARB, { "A16F", 2 } }, { GL_ALPHA32F_ARB, { "A32F", 4 } }, + { GL_R8, { "R8", 1 } }, { GL_R16F, { "R16F", 2 } }, { GL_R32F, { "R32F", 4 } }, { GL_LUMINANCE_ALPHA16F_ARB, { "LA16F", 4 } }, { GL_LUMINANCE_ALPHA32F_ARB, { "LA32F", 8 } }, + { GL_RG8, { "RG8", 2 } }, { GL_RG16F, { "RG16F", 4 } }, { GL_RG32F, { "RG32F", 8 } }, @@ -1095,8 +1098,85 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int break; } } + } + + if ( internalFormat == GL_RGB8 ) + { + /* Scan the texture for green and blue channels' max values + and verify if the green and blue channels are being used or not. */ + + bool hasGreen = false; + bool hasBlue = false; + + c = image->width * image->height; + + for ( int l = 0; l < numLayers; l++ ) + { + scan = dataArray[ l ]; + + for ( i = 0; i < c * 4; i += 4 ) + { + if ( scan[ i + 2 ] != 0 ) + { + // We need GL_RGB8. + hasBlue = true; + break; + } + + if ( scan[ i + 1 ] != 0 ) + { + hasGreen = true; + + if ( !glConfig.textureRGAvailable ) + { + // We can't store RG so we can stop there and use GL_RGB8. + break; + } + // Else continue to make sure there is no blue at all. + } + + // Else use GL_RED or GL_R8. + } + + if ( hasBlue || ( hasGreen && !glConfig.textureRGAvailable ) ) + { + break; + } + } - internalFormat = hasAlpha ? GL_RGBA8 : GL_RGB8; + if ( hasBlue ) + { + // Keep GL_RGB8. + } + else if ( hasGreen ) + { + if ( !glConfig.textureRGAvailable ) + { + // Keep GL_RGB8. + } + else + { + if ( isSRGB && !glConfig.textureSrgbRG8Available ) + { + // Keep GL_RGB8. + } + else + { + internalFormat = GL_RG8; + } + } + } + else + { + if ( isSRGB && !glConfig.textureSrgbR8Available ) + { + // Keep GL_RGB8. + } + else + { + internalFormat = glConfig.textureRGAvailable ? GL_R8 : GL_RED; + } + } } } diff --git a/src/engine/sys/sdl_glimp.cpp b/src/engine/sys/sdl_glimp.cpp index c15add9f0e..c3c4bc9d10 100644 --- a/src/engine/sys/sdl_glimp.cpp +++ b/src/engine/sys/sdl_glimp.cpp @@ -110,6 +110,8 @@ static Cvar::Cvar r_arb_texture_barrier( "r_arb_texture_barrier", "Use GL_ARB_texture_barrier if available", Cvar::NONE, true ); static Cvar::Cvar r_arb_texture_gather( "r_arb_texture_gather", "Use GL_ARB_texture_gather if available", Cvar::NONE, true ); +static Cvar::Cvar r_arb_texture_rg( "r_arb_texture_rg", + "Use GL_ARB_texture_rg if available", Cvar::NONE, true ); static Cvar::Cvar r_arb_uniform_buffer_object( "r_arb_uniform_buffer_object", "Use GL_ARB_uniform_buffer_object if available", Cvar::NONE, true ); static Cvar::Cvar r_arb_vertex_attrib_binding( "r_arb_vertex_attrib_binding", @@ -124,8 +126,10 @@ static Cvar::Cvar r_ext_texture_float( "r_ext_texture_float", "Use GL_EXT_texture_float if available", Cvar::NONE, true ); static Cvar::Cvar r_ext_texture_integer( "r_ext_texture_integer", "Use GL_EXT_texture_integer if available", Cvar::NONE, true ); -static Cvar::Cvar r_ext_texture_rg( "r_ext_texture_rg", - "Use GL_EXT_texture_rg if available", Cvar::NONE, true ); +static Cvar::Cvar r_ext_texture_srgb_r8( "r_ext_texture_srgb_r8", + "Use GL_EXT_texture_sRGB_R8 if available", Cvar::NONE, true ); +static Cvar::Cvar r_ext_texture_srgb_rg8( "r_ext_texture_srgb_rg8", + "Use GL_EXT_texture_sRGB_RG8 if available", Cvar::NONE, true ); static Cvar::Cvar r_khr_debug( "r_khr_debug", "Use GL_KHR_debug if available", Cvar::NONE, true ); static Cvar::Cvar r_khr_shader_subgroup( "r_khr_shader_subgroup", @@ -2034,6 +2038,7 @@ static void GLimp_InitExtensions() Cvar::Latch( r_arb_sync ); Cvar::Latch( r_arb_texture_barrier ); Cvar::Latch( r_arb_texture_gather ); + Cvar::Latch( r_arb_texture_rg ); Cvar::Latch( r_arb_uniform_buffer_object ); Cvar::Latch( r_arb_vertex_attrib_binding ); Cvar::Latch( r_ext_draw_buffers ); @@ -2041,7 +2046,7 @@ static void GLimp_InitExtensions() Cvar::Latch( r_ext_texture_filter_anisotropic ); Cvar::Latch( r_ext_texture_float ); Cvar::Latch( r_ext_texture_integer ); - Cvar::Latch( r_ext_texture_rg ); + Cvar::Latch( r_ext_texture_srgb_r8 ); Cvar::Latch( r_khr_debug ); Cvar::Latch( r_khr_shader_subgroup ); @@ -2153,7 +2158,7 @@ static void GLimp_InitExtensions() && glConfig.gpuShader4Available; // made required in OpenGL 3.0 - glConfig.textureRGAvailable = LOAD_EXTENSION_WITH_TEST( ExtFlag_CORE, ARB_texture_rg, r_ext_texture_rg.Get() ); + glConfig.textureRGAvailable = LOAD_EXTENSION_WITH_TEST( ExtFlag_CORE, ARB_texture_rg, r_arb_texture_rg.Get() ); { bool textureGatherEnabled = r_arb_texture_gather.Get(); @@ -2223,6 +2228,11 @@ static void GLimp_InitExtensions() // made required in OpenGL 3.0 glConfig.textureCompressionRGTCAvailable = LOAD_EXTENSION( ExtFlag_CORE, ARB_texture_compression_rgtc ); + glConfig.textureSrgbR8Available = LOAD_EXTENSION_WITH_TEST( ExtFlag_NONE, EXT_texture_sRGB_R8, r_ext_texture_srgb_r8.Get() ); + + // Texture - others + glConfig.textureSrgbRG8Available = LOAD_EXTENSION_WITH_TEST( ExtFlag_NONE, EXT_texture_sRGB_RG8, r_ext_texture_srgb_rg8.Get() ); + // Texture - others glConfig.textureAnisotropyAvailable = false; glConfig.textureAnisotropy = 0.0f; From 6bc3127556ebba21e28925441c3d20840021c230 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 8 Sep 2025 18:14:08 +0200 Subject: [PATCH 04/11] renderer: introduce and use IF_NOALPHA --- src/engine/renderer/tr_bsp.cpp | 4 ++-- src/engine/renderer/tr_image.cpp | 34 ++++++++++++++++---------------- src/engine/renderer/tr_local.h | 3 ++- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 29cb4bb626..bac7a02011 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -483,8 +483,8 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) return; } - int lightMapBits = IF_LIGHTMAP | IF_NOPICMIP; - int deluxeMapBits = IF_NORMALMAP | IF_NOPICMIP; + int lightMapBits = IF_LIGHTMAP | IF_NOALPHA | IF_NOPICMIP; + int deluxeMapBits = IF_NORMALMAP | IF_NOALPHA | IF_NOPICMIP; if ( tr.worldLinearizeLightMap ) { diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 232c76a934..01e23769d0 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -852,10 +852,13 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int int mipWidth, mipHeight, mipLayers, mipSize, blockSize = 0; int i, c; const byte *scan; + + bool isSRGB = image->bits & IF_SRGB; + bool isAlpha = !( image->bits & IF_NOALPHA ); + GLenum target; GLenum format = GL_RGBA; - GLenum internalFormat = GL_RGB; - bool isSRGB = image->bits & IF_SRGB; + GLenum internalFormat = isAlpha ? GL_RGBA : GL_RGB; static const vec4_t oneClampBorder = { 1, 1, 1, 1 }; static const vec4_t zeroClampBorder = { 0, 0, 0, 1 }; @@ -1053,15 +1056,12 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int } else { - // lightmap does not have alpha channel - if ( image->bits & IF_LIGHTMAP ) - { - internalFormat = GL_RGB8; - } - else - { - internalFormat = GL_RGBA8; - } + internalFormat = GL_RGBA8; + } + + if ( internalFormat == GL_RGBA8 && !isAlpha ) + { + internalFormat = GL_RGB8; } // Detect formats. @@ -2689,7 +2689,7 @@ static void R_CreateBlackCubeImage() } imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; @@ -2716,7 +2716,7 @@ static void R_CreateWhiteCubeImage() } imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; @@ -2759,7 +2759,7 @@ static void R_CreateColorGradeImage() } imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; @@ -2784,7 +2784,7 @@ void R_CreateBuiltinImages() memset( data, 255, sizeof( data ) ); imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_REPEAT; @@ -2798,11 +2798,11 @@ void R_CreateBuiltinImages() // generate a default normalmap with a fully opaque heightmap (no displacement) Vector4Set( data, 128, 128, 255, 255 ); - imageParams.bits = IF_NOPICMIP | IF_NORMALMAP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA | IF_NORMALMAP; tr.flatImage = R_CreateImage( "$flat", ( const byte ** ) &dataPtr, 1, 1, 1, imageParams ); - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; // Don't reuse previously set data, we test the values for selecting the upload format. diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 9d58e99b3d..3169e9457c 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -509,7 +509,8 @@ enum class ssaoMode { IF_BC4 = BIT( 22 ), IF_BC5 = BIT( 23 ), IF_RGBA32UI = BIT( 24 ), - IF_HOMEPATH = BIT( 25 ) + IF_HOMEPATH = BIT( 25 ), + IF_NOALPHA = BIT( 26 ) }; enum class filterType_t From dc73fb6bcd9cad67275e87b83cf0a429856c6437 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 13 Sep 2025 17:44:01 +0200 Subject: [PATCH 05/11] tr_image: flag glyph image with IF_ALPHA --- src/engine/renderer/tr_image.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 01e23769d0..6d071a30f0 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1628,7 +1628,7 @@ image_t *R_CreateGlyph( const char *name, const byte *pic, int width, int height image->texture->target = GL_TEXTURE_2D; image->width = width; image->height = height; - image->bits = IF_NOPICMIP; + image->bits = IF_NOPICMIP | IF_ALPHA; image->filterType = filterType_t::FT_LINEAR; image->wrapType = wrapTypeEnum_t::WT_CLAMP; From 41bd12f5c1dbdc3750a2256d34e54a97ddd9b6ae Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 14 Sep 2025 00:28:07 +0200 Subject: [PATCH 06/11] tr_image: document the GL_RGB format --- src/engine/renderer/tr_image.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 6d071a30f0..9b14da02d5 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -163,6 +163,7 @@ class ListImagesCmd : public Cmd::StaticCmd "If internalformat is specified as a base internal format, the GL stores the resulting texture with internal component resolutions of its own choosing, referred to as the effective internal format." Use 4 bytes as an estimate: */ + { GL_RGB, { "RGB", 3 } }, { GL_RGBA, { "RGBA", 4 } }, { GL_RED, { "RED", 1 } }, From 0d4bd311f762bf5626fd000e6ec776c898c783aa Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 14 Sep 2025 00:38:59 +0200 Subject: [PATCH 07/11] tr_image: add some GL format checks and asserts --- src/engine/renderer/tr_image.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 9b14da02d5..e2cc719b43 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1181,6 +1181,29 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int } } + // Make sure we prefer GL_R8 when ARB_texture_rg is available. + ASSERT( !( internalFormat == GL_RED && glConfig.textureRGAvailable ) ); + // Make sure we only use GL_R8 when ARB_texture_rg is available. + ASSERT( !( internalFormat == GL_R8 && !glConfig.textureRGAvailable ) ); + // Make sure we only use GL_RG8 when ARB_texture_rg is available. + ASSERT( !( internalFormat == GL_RG8 && !glConfig.textureRGAvailable ) ); + // Make sure we only use GL_SR8_EXT when EXT_texture_sRGB_R8 is available. + ASSERT( !( internalFormat == GL_R8 && isSRGB && !glConfig.textureSrgbR8Available ) ); + // Make sure we only use GL_SRG8_EXT when EXT_texture_sRGB_RG8 is available. + ASSERT( !( internalFormat == GL_RG8 && isSRGB && !glConfig.textureSrgbRG8Available ) ); + + // Make sure we prefer GL_RGB but don't enforce it. GL_RGB is used when we don't set a format. + if ( internalFormat == GL_RGBA ) + { + Log::Warn( "An explicit format should be used instead of GL_RGB for image %s", name ); + } + + // Make sure we prefer GL_RGBA but don't enforce it. GL_RGBA is used when we don't set a format. + if ( internalFormat == GL_RGBA ) + { + Log::Warn( "An explicit format should be used instead of GL_RGBA for image %s", name ); + } + Log::Debug( "Uploading image %s (%d×%d, %d layers, %0#x type, %0#x format)", name, scaledWidth, scaledHeight, numLayers, image->type, internalFormat ); // 3D textures are uploaded in slices via glTexSubImage3D, From 43c4229bd186f16efee58b9762647d91a21156e1 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 27 Sep 2025 03:48:38 +0200 Subject: [PATCH 08/11] tr_image: reshape the cinematic image generation a bit --- src/engine/renderer/tr_image.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index e2cc719b43..aff8d13a69 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -2826,12 +2826,14 @@ void R_CreateBuiltinImages() tr.flatImage = R_CreateImage( "$flat", ( const byte ** ) &dataPtr, 1, 1, 1, imageParams ); + /* Generate cinematic frames. + It is empty data to be filled by the cinematic code, but + we fill it with non-zero values so the format detector keeps all color channels. */ + memset( data, 255, sizeof( data ) ); + imageParams.bits = IF_NOPICMIP | IF_NOALPHA; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; - // Don't reuse previously set data, we test the values for selecting the upload format. - memset( data, 255, sizeof( data ) ); - size_t numCinematicImages = 0; for ( image_t * &image : tr.cinematicImage ) { From c458d45b66fcbfa4dc3c381ecf3edcff280dbd3b Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 27 Sep 2025 04:44:43 +0200 Subject: [PATCH 09/11] tr_image: reshape the default image painting a bit --- src/engine/renderer/tr_image.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index aff8d13a69..9fba5bd493 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -2517,23 +2517,18 @@ R_CreateDefaultImage static void R_CreateDefaultImage() { constexpr int DEFAULT_SIZE = 128; - int x; byte data[ DEFAULT_SIZE ][ DEFAULT_SIZE ][ 4 ]; byte *dataPtr = &data[0][0][0]; // the default image will be a box, to allow you to see the mapping coordinates memset( data, 32, sizeof( data ) ); - for ( x = 0; x < DEFAULT_SIZE; x++ ) + for ( int x = 0; x < DEFAULT_SIZE; x++ ) { - data[ 0 ][ x ][ 0 ] = data[ 0 ][ x ][ 1 ] = data[ 0 ][ x ][ 2 ] = data[ 0 ][ x ][ 3 ] = 255; - data[ x ][ 0 ][ 0 ] = data[ x ][ 0 ][ 1 ] = data[ x ][ 0 ][ 2 ] = data[ x ][ 0 ][ 3 ] = 255; - - data[ DEFAULT_SIZE - 1 ][ x ][ 0 ] = - data[ DEFAULT_SIZE - 1 ][ x ][ 1 ] = data[ DEFAULT_SIZE - 1 ][ x ][ 2 ] = data[ DEFAULT_SIZE - 1 ][ x ][ 3 ] = 255; - - data[ x ][ DEFAULT_SIZE - 1 ][ 0 ] = - data[ x ][ DEFAULT_SIZE - 1 ][ 1 ] = data[ x ][ DEFAULT_SIZE - 1 ][ 2 ] = data[ x ][ DEFAULT_SIZE - 1 ][ 3 ] = 255; + Vector4Set( data[ 0 ][ x ], 255, 255, 255, 255 ); + Vector4Set( data[ x ][ 0 ], 255, 255, 255, 255 ); + Vector4Set( data[ DEFAULT_SIZE - 1 ][ x ], 255, 255, 255, 255 ); + Vector4Set( data[ x ][ DEFAULT_SIZE - 1 ], 255, 255, 255, 255 ); } imageParams_t imageParams = {}; From bcb66ee9bdc980745a21ce590e554af610536037 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 27 Sep 2025 04:48:51 +0200 Subject: [PATCH 10/11] tr_image: make some iterators more local --- src/engine/renderer/tr_image.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 9fba5bd493..0ed882d8d1 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -851,7 +851,6 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int const byte *data; byte *scaledBuffer = nullptr; int mipWidth, mipHeight, mipLayers, mipSize, blockSize = 0; - int i, c; const byte *scan; bool isSRGB = image->bits & IF_SRGB; @@ -1075,7 +1074,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int internalFormat = GL_RGB8; - c = image->width * image->height; + int c = image->width * image->height; /* A normalmap may have the heightmap in the alpha channel, an opaque alpha channel means no displacement, so we can enable @@ -1085,7 +1084,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int { scan = dataArray[ l ]; - for ( i = 0; i < c * 4; i += 4 ) + for ( int i = 0; i < c * 4; i += 4 ) { if ( scan[ i + 3 ] != 255 ) { @@ -1109,13 +1108,13 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int bool hasGreen = false; bool hasBlue = false; - c = image->width * image->height; + int c = image->width * image->height; for ( int l = 0; l < numLayers; l++ ) { scan = dataArray[ l ]; - for ( i = 0; i < c * 4; i += 4 ) + for ( int i = 0; i < c * 4; i += 4 ) { if ( scan[ i + 2 ] != 0 ) { @@ -1213,7 +1212,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int mipHeight = scaledHeight; mipLayers = numLayers; - for( i = 0; i < numMips; i++ ) { + for( int i = 0; i < numMips; i++ ) { GL_TexImage3D( GL_TEXTURE_3D, i, internalFormat, scaledWidth, scaledHeight, mipLayers, 0, format, GL_UNSIGNED_BYTE, nullptr, isSRGB ); if( mipWidth > 1 ) mipWidth >>= 1; @@ -1226,7 +1225,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int if( dataArray ) scaledBuffer = (byte*) ri.Hunk_AllocateTempMemory( sizeof( byte ) * scaledWidth * scaledHeight * 4 ); - for ( i = 0; i < numLayers; i++ ) + for ( int i = 0; i < numLayers; i++ ) { if( dataArray ) data = dataArray[ i ]; @@ -1247,7 +1246,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int } if( image->bits & IF_NORMALMAP ) { - c = scaledWidth * scaledHeight; + int c = scaledWidth * scaledHeight; for ( int j = 0; j < c; j++ ) { vec3_t n; @@ -1324,7 +1323,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int mipHeight = scaledHeight; mipLayers = numLayers; - for ( i = 0; i < numMips; i++ ) + for ( int i = 0; i < numMips; i++ ) { mipSize = ((mipWidth + 3) >> 2) * ((mipHeight + 3) >> 2) * blockSize; From b32be39fe7b7d0ee7fd23de8ae608862aebfbd7f Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sat, 13 Sep 2025 21:20:00 +0200 Subject: [PATCH 11/11] renderer: make possible to compress uncompressed textures Add: - r_compressColormaps - r_compressSkyboxes --- src/engine/renderer/tr_backend.cpp | 2 -- src/engine/renderer/tr_image.cpp | 18 +++++++++++++++ src/engine/renderer/tr_local.h | 3 ++- src/engine/renderer/tr_shader.cpp | 37 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index cd1d234aa3..1cdb7f49df 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -801,12 +801,10 @@ static GLint GL_ToSRGB( GLint internalFormat, bool isSRGB ) return GL_SRGB8; case GL_RGBA8: return GL_SRGB8_ALPHA8; -#if 0 // Not used in the codebase. case GL_COMPRESSED_RGB: return GL_COMPRESSED_SRGB; case GL_COMPRESSED_RGBA: return GL_COMPRESSED_SRGB_ALPHA; -#endif case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 0ed882d8d1..af8aa9e69c 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -197,6 +197,7 @@ class ListImagesCmd : public Cmd::StaticCmd If internalformat is one of the six generic compressed internal formats, its value is replaced by the symbolic constant for a specific compressed internal format of the GL’s choosing with the same base internal format." Use 4x4 blocks with 8 bytes per block here as an estimate: */ + { GL_COMPRESSED_RGB, { "RGBC", 8 } }, { GL_COMPRESSED_RGBA, { "RGBAC", 8 } }, /* https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_compression_s3tc.txt @@ -378,6 +379,7 @@ class ListImagesCmd : public Cmd::StaticCmd { switch ( image->internalFormat ) { // Compressed formats encode blocks of 4x4 texels + case GL_COMPRESSED_RGB: case GL_COMPRESSED_RGBA: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_SIGNED_RED_RGTC1: @@ -1203,6 +1205,22 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int Log::Warn( "An explicit format should be used instead of GL_RGBA for image %s", name ); } + if ( image->bits & IF_COMPRESS ) + { + switch ( internalFormat ) + { + case GL_RED: + case GL_RGB8: + internalFormat = GL_COMPRESSED_RGB; + break; + case GL_RGBA8: + internalFormat = GL_COMPRESSED_RGBA; + break; + default: + break; + } + } + Log::Debug( "Uploading image %s (%d×%d, %d layers, %0#x type, %0#x format)", name, scaledWidth, scaledHeight, numLayers, image->type, internalFormat ); // 3D textures are uploaded in slices via glTexSubImage3D, diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 3169e9457c..8c9abd05d4 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -510,7 +510,8 @@ enum class ssaoMode { IF_BC5 = BIT( 23 ), IF_RGBA32UI = BIT( 24 ), IF_HOMEPATH = BIT( 25 ), - IF_NOALPHA = BIT( 26 ) + IF_NOALPHA = BIT( 26 ), + IF_COMPRESS = BIT(27) }; enum class filterType_t diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index bffc8d2f72..e83caf5987 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -87,6 +87,11 @@ static Cvar::Cvar r_portalDefaultRange( Cvar::Cvar r_depthShaders( "r_depthShaders", "use depth pre-pass shaders", Cvar::CHEAT, true); +Cvar::Cvar r_compressColormaps( + "r_compressColormaps", "compress uncompressed color maps", Cvar::NONE, false); +Cvar::Cvar r_compressSkyboxes( + "r_compressSkyboxes", "compress uncompressed skyboxes", Cvar::NONE, false); + struct delayedStageTexture_t { bool active; stageType_t type; @@ -1518,6 +1523,25 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, } } + if ( r_compressColormaps.Get() ) + { + if ( ! ( shader.registerFlags & RSF_2D ) ) + { + switch ( type ) + { + case stageType_t::ST_COLORMAP: + case stageType_t::ST_DIFFUSEMAP: + case stageType_t::ST_GLOWMAP: + case stageType_t::ST_REFLECTIONMAP: + case stageType_t::ST_SKYBOXMAP: + imageParams.bits |= IF_COMPRESS; + break; + default: + break; + } + } + } + // determine image options if ( stage->overrideNoPicMip || shader.noPicMip || stage->highQuality || stage->forceHighQuality ) { @@ -3719,6 +3743,11 @@ static void ParseSkyParms( const char **text ) imageParams.bits |= IF_SRGB; } + if ( r_compressSkyboxes.Get() ) + { + imageParams.bits |= IF_COMPRESS; + } + shader.sky.outerbox = R_FindCubeImage( prefix, imageParams ); if ( !shader.sky.outerbox ) @@ -6410,6 +6439,12 @@ shader_t *R_FindShader( const char *name, int flags ) imageParams.bits |= IF_SRGB; } + // HACK: Detect color grade images with RSF_NOMIP. + if ( r_compressColormaps.Get() && ! ( flags & RSF_NOMIP ) ) + { + imageParams.bits |= IF_COMPRESS; + } + image = R_FindImageFile( fileName, imageParams ); } @@ -7034,6 +7069,8 @@ void R_InitShaders() Cvar::Latch(r_dpMaterial); Cvar::Latch(r_depthShaders); Cvar::Latch(r_portalDefaultRange); + Cvar::Latch(r_compressColormaps); + Cvar::Latch(r_compressSkyboxes); memset( shaderTableHashTable, 0, sizeof( shaderTableHashTable ) ); memset( shaderHashTable, 0, sizeof( shaderHashTable ) );