Skip to content

renderer: implement native sRGB support #1687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/common/Color.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "Compiler.h"
#include "Math.h"

inline float convertFromSRGB( float f, bool accurate = true )
{
if ( accurate )
{
return f <= 0.04045f ? f * (1.0f / 12.92f) : pow((f + 0.055f) * (1.0f / 1.055f), 2.4f);
}

return pow( f, 2.2f );
}

inline void convertFromSRGB( float* v, bool accurate = true )
{
v[ 0 ] = convertFromSRGB( v[ 0 ], accurate );
v[ 1 ] = convertFromSRGB( v[ 1 ], accurate );
v[ 2 ] = convertFromSRGB( v[ 2 ], accurate );
}

inline void convertFromSRGB( byte* bytes, bool accurate = true )
{
vec3_t v;
VectorScale( bytes, 1.0f / 255.0f, v );
convertFromSRGB( v, accurate );
VectorScale( v, 255.0f, bytes );
}

namespace Color {

/*
Expand Down Expand Up @@ -256,6 +281,22 @@ class BasicColor
data_[ 3 ] = v;
}

static constexpr component_type ConvertFromSRGB( component_type v, bool accurate = true ) NOEXCEPT
{
float f = float( v ) / float( component_max );
f = convertFromSRGB( f, accurate );
return component_type( f * float( component_max ) );
}

constexpr BasicColor ConvertFromSRGB( bool accurate = true ) const NOEXCEPT
{
return BasicColor(
ConvertFromSRGB( Red(), accurate ),
ConvertFromSRGB( Green(), accurate ),
ConvertFromSRGB( Blue(), accurate ),
Alpha() );
}

CONSTEXPR_FUNCTION_RELAXED BasicColor& operator*=( float factor ) NOEXCEPT
{
*this = *this * factor;
Expand Down
6 changes: 6 additions & 0 deletions src/engine/renderer/gl_shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,11 @@ static std::string GenEngineConstants() {
AddConst( str, "r_RimExponent", r_rimExponent->value );
}

if ( r_accurateSRGB.Get() )
{
AddDefine( str, "r_accurateSRGB", 1 );
}

if ( r_showLightTiles->integer )
{
AddDefine( str, "r_showLightTiles", 1 );
Expand Down Expand Up @@ -2801,6 +2806,7 @@ GLShader_cameraEffects::GLShader_cameraEffects() :
u_CurrentMap( this ),
u_GlobalLightFactor( this ),
u_ColorModulate( this ),
u_SRGB( this ),
u_Tonemap( this ),
u_TonemapParms( this ),
u_TonemapExposure( this ),
Expand Down
13 changes: 13 additions & 0 deletions src/engine/renderer/gl_shader.h
Original file line number Diff line number Diff line change
Expand Up @@ -3073,6 +3073,18 @@ class u_InverseGamma :
}
};

class u_SRGB :
GLUniform1Bool {
public:
u_SRGB( GLShader* shader ) :
GLUniform1Bool( shader, "u_SRGB", true ) {
}

void SetUniform_SRGB( bool tonemap ) {
this->SetValue( tonemap );
}
};

class u_Tonemap :
GLUniform1Bool {
public:
Expand Down Expand Up @@ -3579,6 +3591,7 @@ class GLShader_cameraEffects :
public u_CurrentMap,
public u_GlobalLightFactor,
public u_ColorModulate,
public u_SRGB,
public u_Tonemap,
public u_TonemapParms,
public u_TonemapExposure,
Expand Down
21 changes: 21 additions & 0 deletions src/engine/renderer/glsl_source/cameraEffects_fp.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ uniform sampler3D u_ColorMap3D;
uniform vec4 u_ColorModulate;
uniform float u_GlobalLightFactor; // 1 / tr.identityLight
uniform float u_InverseGamma;
uniform bool u_SRGB;

void convertToSRGB(inout vec3 color) {
#if defined(r_accurateSRGB)
float threshold = 0.0031308f;

bvec3 cutoff = lessThan(color, vec3(threshold));
vec3 low = vec3(12.92f) * color;
vec3 high = vec3(1.055f) * pow(color, vec3(1.0f / 2.4f)) - vec3(0.055f);

color = mix(high, low, cutoff);
#else
float inverse = 0.4545454f; // 1 / 2.2
color = pow(color, vec3(inverse));
#endif
}

// Tone mapping is not available when high-precision float framebuffer isn't enabled or supported.
#if defined(r_highPrecisionRendering) && defined(HAVE_ARB_texture_float)
Expand Down Expand Up @@ -59,6 +75,11 @@ void main()
vec4 color = texture2D(u_CurrentMap, st);
color *= u_GlobalLightFactor;

if ( u_SRGB )
{
convertToSRGB( color.rgb );
}

#if defined(r_highPrecisionRendering) && defined(HAVE_ARB_texture_float)
if( u_Tonemap ) {
color.rgb = TonemapLottes( color.rgb * u_TonemapExposure );
Expand Down
123 changes: 119 additions & 4 deletions src/engine/renderer/tr_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,99 @@ void GL_VertexAttribPointers( uint32_t attribBits )
}
}

static GLint GL_ToSRGB( GLint internalFormat, bool isSRGB )
{
if ( !isSRGB )
{
return internalFormat;
}

auto convert = []( GLint format )
{
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:
return GL_SR8_EXT;
#endif
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;
#if 0 // Internal formats, should not be used directly.
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:
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;
#if 0 // Not used in the codebase,
/* Core 4.2, ARB_texture_compression_bptc extension.
See: https://github.com/KhronosGroup/OpenGL-Registry/blob/main/extensions/ARB/ARB_texture_compression_bptc.txt */
case GL_COMPRESSED_RGBA_BPTC_UNORM:
return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
#endif
default:
return format;
}
};

GLint finalFormat = convert( internalFormat );

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 );
}

void GL_CompressedTexImage2D( GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data, bool isSRGB )
{
GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB );

glCompressedTexImage2D( target, level, finalFormat, width, height, border, imageSize, data );
}

void GL_CompressedTexSubImage3D( GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum internalFormat, GLsizei size, const void *data, bool isSRGB )
{
GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB );

glCompressedTexSubImage3D( target, level, xOffset, yOffset, zOffset, width, height, depth, finalFormat, size, data );
}

/*
================
RB_Hyperspace
Expand Down Expand Up @@ -1289,7 +1382,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;
}

Expand Down Expand Up @@ -1535,6 +1630,8 @@ void RB_CameraPostFX() {

gl_cameraEffectsShader->SetUniform_InverseGamma( 1.0 / r_gamma->value );

gl_cameraEffectsShader->SetUniform_SRGB( tr.worldLinearizeTexture );

const bool tonemap = r_toneMapping.Get() && r_highPrecisionRendering.Get() && glConfig2.textureFloatAvailable;
if ( tonemap ) {
vec4_t tonemapParms { r_toneMappingContrast.Get(), r_toneMappingHighlightsCompressionSpeed.Get() };
Expand Down Expand Up @@ -2665,12 +2762,24 @@ void RE_UploadCinematic( int cols, int rows, const byte *data, int client, bool
GL_Bind( tr.cinematicImage[ client ] );

// if the scratchImage isn't in the format we want, specify it as a new texture
/* HACK: This also detects we start playing a video to set the appropriate colorspace
because this assumes a video cannot have a 1×1 size (the RoQ format expects the size
to be a multiples of 4). */
if ( cols != tr.cinematicImage[ client ]->width || rows != tr.cinematicImage[ client ]->height )
{
tr.cinematicImage[ client ]->width = tr.cinematicImage[ client ]->uploadWidth = cols;
tr.cinematicImage[ client ]->height = tr.cinematicImage[ client ]->uploadHeight = rows;

glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
bool isSRGB = tr.worldLinearizeTexture;

// Makes sure listImages lists the colorspace properly.
if ( isSRGB )
{
tr.cinematicImage[ client ]->bits |= IF_SRGB;
}
// No need to delete the bit otherwise because R_InitImages() is called at every map load.

GL_TexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data, isSRGB );

glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
Expand Down Expand Up @@ -2900,7 +3009,10 @@ const RenderCommand *Poly2dCommand::ExecuteSelf( ) const
tess.verts[ tess.numVertexes ].texCoords[ 0 ] = verts[ i ].st[ 0 ];
tess.verts[ tess.numVertexes ].texCoords[ 1 ] = verts[ i ].st[ 1 ];

tess.verts[ tess.numVertexes ].color = Color::Adapt( verts[ i ].modulate );
Color::Color32Bit color = Color::Adapt( verts[ i ].modulate );
color = tr.convertColorFromSRGB( color );
tess.verts[ tess.numVertexes ].color = color;

tess.numVertexes++;
}

Expand Down Expand Up @@ -2957,7 +3069,10 @@ const RenderCommand *Poly2dIndexedCommand::ExecuteSelf( ) const
tess.verts[ tess.numVertexes ].texCoords[ 0 ] = verts[ i ].st[ 0 ];
tess.verts[ tess.numVertexes ].texCoords[ 1 ] = verts[ i ].st[ 1 ];

tess.verts[ tess.numVertexes ].color = Color::Adapt( verts[ i ].modulate );
Color::Color32Bit color = Color::Adapt( verts[ i ].modulate );
color = tr.convertColorFromSRGB( color );
tess.verts[ tess.numVertexes ].color = color;

tess.numVertexes++;
}

Expand Down
Loading
Loading