diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 43f74375a0..a702a9056b 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -739,12 +739,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: @@ -753,7 +758,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: diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 7da251d6b0..50f20af931 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 b430bedcd4..273e2b39d3 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -163,7 +163,9 @@ 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 } }, { GL_RGB8, { "RGB8", 3 } }, { GL_RGBA8, { "RGBA8", 4 } }, @@ -176,10 +178,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 } }, @@ -846,12 +850,14 @@ 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; + 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 }; @@ -1041,33 +1047,153 @@ 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 + internalFormat = GL_RGBA8; + } - c = image->width * image->height; - scan = dataArray[0]; + if ( internalFormat == GL_RGBA8 && !isAlpha ) + { + internalFormat = GL_RGB8; + } - // lightmap does not have alpha channel + // 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. */ - // 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 + internalFormat = GL_RGB8; - internalFormat = GL_RGB8; + 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 + the alpha channel everytime it is used, even for normalmap. */ + + for ( int l = 0; l < numLayers; l++ ) + { + scan = dataArray[ l ]; + + for ( int i = 0; i < c * 4; i += 4 ) + { + if ( scan[ i + 3 ] != 255 ) + { + internalFormat = GL_RGBA8; + break; + } + } - if ( !( image->bits & IF_LIGHTMAP ) ) + if ( internalFormat == GL_RGBA8 ) + { + break; + } + } + } + + if ( internalFormat == GL_RGB8 ) { - for ( i = 0; i < c; i++ ) + /* 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; + + int c = image->width * image->height; + + for ( int l = 0; l < numLayers; l++ ) { - if ( scan[ i * 4 + 3 ] != 255 ) + scan = dataArray[ l ]; + + for ( int 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 ) ) { - internalFormat = GL_RGBA8; break; } } + + 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; + } + } } } + // 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, @@ -1077,7 +1203,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; @@ -1090,7 +1216,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 ]; @@ -1111,7 +1237,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; @@ -1179,7 +1305,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; @@ -1501,7 +1627,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; @@ -2366,23 +2492,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 = {}; @@ -2554,7 +2675,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; @@ -2581,7 +2702,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; @@ -2624,7 +2745,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; @@ -2649,7 +2770,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; @@ -2663,16 +2784,18 @@ 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.wrapType = wrapTypeEnum_t::WT_CLAMP; - - // Don't reuse previously set data, we test the values for selecting the upload format. + /* 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; + size_t numCinematicImages = 0; for ( image_t * &image : tr.cinematicImage ) { diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 0ba092ed53..5b88f797dc 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -498,7 +498,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 diff --git a/src/engine/renderer/tr_public.h b/src/engine/renderer/tr_public.h index 3864e278b4..77f55fca05 100644 --- a/src/engine/renderer/tr_public.h +++ b/src/engine/renderer/tr_public.h @@ -139,6 +139,8 @@ struct GLConfig bool gpuShader4Available; bool gpuShader5Available; bool textureGatherAvailable; + bool textureSrgbR8Available; + bool textureSrgbRG8Available; int maxDrawBuffers; float maxTextureAnisotropy; diff --git a/src/engine/sys/sdl_glimp.cpp b/src/engine/sys/sdl_glimp.cpp index 1580598a94..fa0c55f662 100644 --- a/src/engine/sys/sdl_glimp.cpp +++ b/src/engine/sys/sdl_glimp.cpp @@ -108,6 +108,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", @@ -122,8 +124,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", @@ -2023,6 +2027,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 ); @@ -2030,7 +2035,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 ); @@ -2140,7 +2145,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(); @@ -2210,6 +2215,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;