diff --git a/src/common/Color.h b/src/common/Color.h index 8c98ebc74c..727b082d81 100644 --- a/src/common/Color.h +++ b/src/common/Color.h @@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Compiler.h" #include "Math.h" +#define convertFromSRGB( v ) (v <= 0.04045f ? v * (1.0f / 12.92f) : pow((v + 0.055f) * (1.0f / 1.055f), 2.4f)) + namespace Color { /* @@ -256,6 +258,20 @@ class BasicColor data_[ 3 ] = v; } + CONSTEXPR_FUNCTION_RELAXED component_type ConvertFromSRGB( component_type v ) NOEXCEPT + { + float f = float( v ) / 255.0f; + f = convertFromSRGB( f ); + return component_type( f * 255 ); + } + + CONSTEXPR_FUNCTION_RELAXED void ConvertFromSRGB() NOEXCEPT + { + SetRed( ConvertFromSRGB( Red() ) ); + SetGreen( ConvertFromSRGB( Green() ) ); + SetBlue( ConvertFromSRGB( Blue() ) ); + } + CONSTEXPR_FUNCTION_RELAXED BasicColor& operator*=( float factor ) NOEXCEPT { *this = *this * factor; diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index b0dbce22c9..d4123e0c9e 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -707,6 +707,78 @@ void GL_VertexAttribPointers( uint32_t attribBits ) } } +GLint GL_ToSRGB_( GLint internalFormat, bool isSRGB ) +{ + if ( !isSRGB ) + { + return internalFormat; + } + + switch ( internalFormat ) + { + case GL_RGB: + return GL_SRGB; + case GL_RGBA: + return GL_SRGB_ALPHA; + case GL_RGB8: + return GL_SRGB8; + case GL_RGBA8: + return GL_SRGB8_ALPHA8; + // not used + // case GL_COMPRESSED_RGB: + // return GL_COMPRESSED_SRGB; + case GL_COMPRESSED_RGBA: + return GL_COMPRESSED_SRGB_ALPHA; + // not used, core 4.2, ARB_texture_compression_bptc + // case GL_COMPRESSED_RGBA_BPTC_UNORM: + // return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + default: + return internalFormat; + } +} + +GLint GL_ToSRGB( GLint internalFormat, bool isSRGB ) +{ + GLint finalFormat = GL_ToSRGB_( internalFormat, isSRGB ); + + if ( isSRGB ) + { + if ( finalFormat == internalFormat ) + { + Log::Warn( "Missing sRGB conversion for GL format: %0#x", internalFormat ); + } + else + { + Log::Debug( "Using sRGB GL format: %0#x", finalFormat ); + } + } + + return finalFormat; +} + +void GL_TexImage2D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * data, bool isSRGB ) +{ + GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB ); + + glTexImage2D( target, level, finalFormat, width, height, border, format, type, data ); + +} + +void GL_TexImage3D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * data, bool isSRGB ) +{ + GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB ); + + glTexImage3D( target, level, finalFormat, width, height, depth, border, format, type, data ); +} + /* ================ RB_Hyperspace @@ -1289,7 +1361,9 @@ void RB_RenderGlobalFog() void RB_RenderBloom() { if ( ( backEnd.refdef.rdflags & ( RDF_NOWORLDMODEL | RDF_NOBLOOM ) ) - || !glConfig2.bloom || backEnd.viewParms.portalLevel > 0 ) { + || !glConfig2.bloom || backEnd.viewParms.portalLevel > 0 + || !tr.worldLinearizeTexture ) + { return; } diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 3cd0c9b9be..331df87648 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -451,6 +451,15 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) return; } + int lightmapBits = IF_LIGHTMAP | IF_NOPICMIP; + int deluxemapBits = IF_NORMALMAP | IF_NOPICMIP; + + if ( tr.worldLinearizeLightMap ) + { + lightmapBits |= IF_SRGB; + deluxemapBits |= IF_SRGB; + } + int len = l->filelen; if ( !len ) { @@ -493,7 +502,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) LoadRGBEToBytes( va( "%s/%s", mapName, filename.c_str() ), &ldrImage, &width, &height ); imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP | IF_LIGHTMAP; + imageParams.bits = lightmapBits; imageParams.filterType = filterType_t::FT_DEFAULT; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; @@ -520,7 +529,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) Log::Debug("...loading external lightmap '%s/%s'", mapName, filename); imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP | IF_NORMALMAP; + imageParams.bits = deluxemapBits; imageParams.filterType = filterType_t::FT_DEFAULT; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; @@ -549,7 +558,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) if (!tr.worldDeluxeMapping || i % 2 == 0) { imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP | IF_LIGHTMAP; + imageParams.bits = lightmapBits; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; @@ -559,7 +568,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) else if (tr.worldDeluxeMapping) { imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP | IF_NORMALMAP; + imageParams.bits = deluxemapBits; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; @@ -629,7 +638,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) } imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP | IF_LIGHTMAP; + imageParams.bits = lightmapBits; imageParams.filterType = filterType_t::FT_DEFAULT; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; @@ -972,6 +981,11 @@ static void ParseTriangleSurface( dsurface_t* ds, drawVert_t* verts, bspSurface_ cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); + if ( tr.worldLinearizeLightMap ) + { + cv->verts[ i ].lightColor.ConvertFromSRGB(); + } + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() ); } @@ -3502,6 +3516,12 @@ void R_LoadLightGrid( lump_t *l ) { ambientColor[ j ] = tmpAmbient[ j ] * ( 1.0f / 255.0f ); directedColor[ j ] = tmpDirected[ j ] * ( 1.0f / 255.0f ); + + if ( tr.worldLinearizeLightMap ) + { + ambientColor[ j ] = convertFromSRGB( ambientColor[ j ] ); + directedColor[ j ] = convertFromSRGB( directedColor[ j ] ); + } } const float forceAmbient = r_forceAmbient.Get(); @@ -3766,6 +3786,70 @@ void R_LoadEntities( lump_t *l, std::string &externalEntities ) tr.worldDeluxeMapping = glConfig2.deluxeMapping; } + bool sRGBtex = false; + bool sRGBcolor = false; + bool sRGBlight = false; + + s = strstr( value, "-sRGB" ); + + if ( s && ( s[5] == ' ' || s[5] == '\0' ) ) + { + sRGBtex = true; + sRGBcolor = true; + sRGBlight = true; + } + + s = strstr( value, "-nosRGB" ); + + if ( s && ( s[5] == ' ' || s[5] == '\0' ) ) + { + sRGBtex = false; + sRGBcolor = false; + sRGBlight = true; + } + + if ( strstr( value, "-sRGBlight" ) ) + { + sRGBlight = true; + } + + if ( strstr( value, "-nosRGBlight" ) ) + { + sRGBlight = false; + } + + if ( strstr( value, "-sRGBcolor" ) ) + { + sRGBcolor = true; + } + + if ( strstr( value, "-nosRGBcolor" ) ) + { + sRGBcolor = false; + } + + if ( strstr( value, "-sRGBtex" ) ) + { + sRGBtex = true; + } + + if ( strstr( value, "-nosRGBtex" ) ) + { + sRGBtex = false; + } + + if ( sRGBlight ) + { + Log::Debug("map features lights in sRGB colorspace" ); + tr.worldLinearizeLightMap = true; + } + + if ( sRGBcolor && sRGBtex ) + { + Log::Debug("map features lights computed with linear colors and textures" ); + tr.worldLinearizeTexture = true; + } + continue; } @@ -4517,6 +4601,8 @@ void RE_LoadWorldMap( const char *name ) tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); // set by RE_LoadWorldMap tr.mapLightFactor = 1.0f; // set by RE_LoadWorldMap tr.identityLight = 1.0f; // set by RE_LoadWorldMap + tr.worldLinearizeTexture = false; + tr.worldLinearizeLightMap = false; s_worldData = {}; Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 5a694da0c2..55245dcd19 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -838,6 +838,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int GLenum target; GLenum format = GL_RGBA; GLenum internalFormat = GL_RGB; + bool isSRGB = image->bits & IF_SRGB; static const vec4_t oneClampBorder = { 1, 1, 1, 1 }; static const vec4_t zeroClampBorder = { 0, 0, 0, 1 }; @@ -1076,9 +1077,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int mipLayers = numLayers; for( i = 0; i < numMips; i++ ) { - glTexImage3D( GL_TEXTURE_3D, i, internalFormat, - scaledWidth, scaledHeight, mipLayers, - 0, format, GL_UNSIGNED_BYTE, nullptr ); + GL_TexImage3D( GL_TEXTURE_3D, i, internalFormat, scaledWidth, scaledHeight, mipLayers, 0, format, GL_UNSIGNED_BYTE, nullptr, isSRGB ); if( mipWidth > 1 ) mipWidth >>= 1; if( mipHeight > 1 ) mipHeight >>= 1; @@ -1144,18 +1143,17 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int } break; case GL_TEXTURE_CUBE_MAP: - glTexImage2D( target + i, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, - scaledBuffer ); + GL_TexImage2D( target + i, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer, isSRGB ); break; default: if ( image->bits & IF_PACKED_DEPTH24_STENCIL8 ) { - glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8, nullptr ); + GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8, nullptr, isSRGB ); } else { - glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer ); + GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer, isSRGB ); } break; @@ -1511,7 +1509,9 @@ image_t *R_CreateGlyph( const char *name, const byte *pic, int width, int height image->uploadHeight = height; image->internalFormat = GL_RGBA; - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pic ); + bool isSRGB = true; + + GL_TexImage2D( GL_TEXTURE_2D, 0, image->internalFormat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pic, isSRGB ); GL_CheckErrors(); @@ -2734,7 +2734,7 @@ static void R_CreateColorGradeImage() } imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_SRGB; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; @@ -3007,7 +3007,7 @@ qhandle_t RE_GenerateTexture( const byte *pic, int width, int height ) std::string name = Str::Format( "$generatedTexture%d", tr.numGeneratedTextures++ ); imageParams_t imageParams = {}; - imageParams.bits = IF_NOPICMIP; + imageParams.bits = IF_NOPICMIP | IF_SRGB; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index 76c31833b6..45b0f7e95a 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -869,6 +869,8 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p GL_fboShim.glBindRenderbuffer( GL_RENDERBUFFER, 0 ); glState.currentFBO = nullptr; + glEnable( GL_FRAMEBUFFER_SRGB ); + GL_PolygonMode( GL_FRONT_AND_BACK, GL_FILL ); GL_DepthMask( GL_TRUE ); glDisable( GL_DEPTH_TEST ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 4f47fa0e44..c22f26a372 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -475,6 +475,7 @@ enum class ssaoMode { IF_RGBE = BIT( 15 ), IF_ALPHATEST = BIT( 16 ), // FIXME: this is unused IF_ALPHA = BIT( 17 ), + IF_SRGB = BIT( 18 ), IF_BC1 = BIT( 19 ), IF_BC2 = BIT( 20 ), IF_BC3 = BIT( 21 ), @@ -1146,6 +1147,7 @@ enum class ssaoMode { expression_t deformMagnitudeExp; + bool specularSRGB; bool noFog; // used only for shaders that have fog disabled, so we can enable it for individual stages bool useMaterialSystem = false; @@ -2513,6 +2515,8 @@ enum class ssaoMode { bool worldLightMapping; bool worldDeluxeMapping; bool worldHDR_RGBE; + bool worldLinearizeTexture; + bool worldLinearizeLightMap; lightMode_t lightMode; lightMode_t worldLight; @@ -3020,6 +3024,9 @@ inline bool checkGLErrors() void GL_VertexAttribsState( uint32_t stateBits ); void GL_VertexAttribPointers( uint32_t attribBits ); void GL_Cull( cullType_t cullType ); + GLint GL_ToSRGB( GLint internalFormat, bool isSRGB ); + void GL_TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * data, bool isSRGB ); + void GL_TexImage3D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * data, bool isSRGB ); void R_ShutdownBackend(); /* diff --git a/src/engine/renderer/tr_scene.cpp b/src/engine/renderer/tr_scene.cpp index 388d8a16ab..08f132ce41 100644 --- a/src/engine/renderer/tr_scene.cpp +++ b/src/engine/renderer/tr_scene.cpp @@ -324,6 +324,14 @@ void RE_AddDynamicLightToSceneET( const vec3_t org, float radius, float intensit light->color[ 1 ] = g; light->color[ 2 ] = b; + // Linearize dynamic lights. + if ( tr.worldLinearizeTexture ) + { + light->color[ 0 ] = convertFromSRGB( light->color[ 0 ] ); + light->color[ 1 ] = convertFromSRGB( light->color[ 1 ] ); + light->color[ 2 ] = convertFromSRGB( light->color[ 2 ] ); + } + light->scale = intensity; } diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index abb1a2a8d2..ba8825cae9 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -1761,6 +1761,11 @@ void Tess_ComputeColor( shaderStage_t *pStage ) { rgb = Math::Clamp( RB_EvalExpression( &pStage->rgbExp, 1.0 ), 0.0f, 1.0f ); + if ( tr.worldLinearizeTexture ) + { + rgb = convertFromSRGB ( rgb ); + } + tess.svars.color = Color::White * rgb; break; } @@ -1783,6 +1788,13 @@ void Tess_ComputeColor( shaderStage_t *pStage ) blue = Math::Clamp( RB_EvalExpression( &pStage->blueExp, 1.0 ), 0.0f, 1.0f ); } + if ( tr.worldLinearizeTexture ) + { + red = convertFromSRGB ( red ); + green = convertFromSRGB ( green ); + blue = convertFromSRGB ( blue ); + } + tess.svars.color.SetRed( red ); tess.svars.color.SetGreen( green ); tess.svars.color.SetBlue( blue ); diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 518c4021e1..0c92ac507b 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -1479,6 +1479,17 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, imageParams.minDimension = shader.imageMinDimension; imageParams.maxDimension = shader.imageMaxDimension; + if ( stage->type == stageType_t::ST_COLORMAP + || stage->type == stageType_t::ST_DIFFUSEMAP + || ( stage->type == stageType_t::ST_SPECULARMAP && stage->specularSRGB ) + || stage->type == stageType_t::ST_GLOWMAP + || stage->type == stageType_t::ST_REFLECTIONMAP + || stage->type == stageType_t::ST_SKYBOXMAP + || stage->collapseType != collapseType_t::COLLAPSE_none ) + { + imageParams.bits |= IF_SRGB; + } + // determine image options if ( stage->overrideNoPicMip || shader.noPicMip || stage->highQuality || stage->forceHighQuality ) { @@ -1522,6 +1533,8 @@ static bool LoadMap( shaderStage_t *stage, const char *buffer, stageType_t type, // try to load the image if ( stage->isCubeMap ) { + imageParams.bits |= IF_SRGB; + stage->bundle[ bundleIndex ].image[ 0 ] = R_FindCubeImage( buffer, imageParams ); if ( !stage->bundle[ bundleIndex ].image[ 0 ] ) @@ -3196,6 +3209,10 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) { ParseExpression( text, &stage->deformMagnitudeExp ); } + else if ( !Q_stricmp( token, "specularSRGB" ) ) + { + stage->specularSRGB = true; + } else { Log::Warn("unknown shader stage parameter '%s' in shader '%s'", token, shader.name ); @@ -6169,10 +6186,10 @@ shader_t *R_FindShader( const char *name, shaderType_t type, int flags ) Log::Debug( "loading '%s' image as shader", fileName ); - if( bits & RSF_2D ) + if ( flags & RSF_2D ) { imageParams_t imageParams = {}; - imageParams.bits = bits; + imageParams.bits = bits | IF_SRGB; imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP;