From dc42bdc873f2c64497a1c5418fdda4d3d739af64 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 15 Jun 2021 03:24:36 +0200 Subject: [PATCH] Do gamma correction (r_gamma, r_brightness) in shaders by injecting code to do it into fragment shaders. Can be disabled with r_gammaInShaders 0 refs #385 --- neo/renderer/RenderSystem.cpp | 14 +++++ neo/renderer/RenderSystem_init.cpp | 7 +++ neo/renderer/draw_arb2.cpp | 97 ++++++++++++++++++++++++++++++ neo/renderer/draw_common.cpp | 21 ++++++- neo/renderer/tr_local.h | 3 + neo/sys/glimp.cpp | 43 +++++++++++++ neo/sys/stub/stub_gl.cpp | 1 + 7 files changed, 184 insertions(+), 2 deletions(-) diff --git a/neo/renderer/RenderSystem.cpp b/neo/renderer/RenderSystem.cpp index 2ab73451..722cf1ab 100644 --- a/neo/renderer/RenderSystem.cpp +++ b/neo/renderer/RenderSystem.cpp @@ -246,6 +246,20 @@ static void R_CheckCvars( void ) { r_brightness.ClearModified(); R_SetColorMappings(); } + + if ( r_gammaInShader.IsModified() ) { + r_gammaInShader.ClearModified(); + // reload shaders so they either add or remove the code for setting gamma/brightness in shader + R_ReloadARBPrograms_f( idCmdArgs() ); + + if ( r_gammaInShader.GetBool() ) { + common->Printf( "Will apply r_gamma and r_brightness in shaders\n" ); + GLimp_ResetGamma(); // reset hardware gamma + } else { + common->Printf( "Will apply r_gamma and r_brightness in hardware (possibly on all screens)\n" ); + R_SetColorMappings(); + } + } } /* diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index f14bb059..b070d3f7 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -91,6 +91,7 @@ idCVar r_swapInterval( "r_swapInterval", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVA idCVar r_gamma( "r_gamma", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 3.0f ); idCVar r_brightness( "r_brightness", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 2.0f ); +idCVar r_gammaInShader( "r_gammaInShader", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Set gamma and brightness in shaders instead using hardware gamma" ); idCVar r_renderer( "r_renderer", "best", CVAR_RENDERER | CVAR_ARCHIVE, "hardware specific renderer path to use", r_rendererArgs, idCmdSystem::ArgCompletion_String ); @@ -1757,6 +1758,12 @@ R_SetColorMappings =============== */ void R_SetColorMappings( void ) { + + if ( r_gammaInShader.GetBool() ) { + // nothing to do here + return; + } + int i, j; float g, b; int inf; diff --git a/neo/renderer/draw_arb2.cpp b/neo/renderer/draw_arb2.cpp index 8aaa97da..5aa89243 100644 --- a/neo/renderer/draw_arb2.cpp +++ b/neo/renderer/draw_arb2.cpp @@ -98,6 +98,15 @@ void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) { qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() ); + // DG: brightness and gamma in shader as program.env[4] + if ( r_gammaInShader.GetBool() ) { + // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0/r_gamma + float parm[4]; + parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); + parm[3] = 1.0/r_gamma.GetFloat(); // 1.0/gamma so the shader doesn't have to do this calculation + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm ); + } + // set the textures // texture 1 will be the per-surface bump map @@ -344,6 +353,31 @@ static progDef_t progs[MAX_GLPROGS] = { R_LoadARBProgram ================= */ + +static char* findLineThatStartsWith( char* text, const char* findMe ) { + char* res = strstr( text, findMe ); + while ( res != NULL ) { + // skip whitespace before match, if any + char* cur = res; + if ( cur > text ) { + --cur; + } + while ( cur > text && ( *cur == ' ' || *cur == '\t' ) ) { + --cur; + } + // now we should be at a newline (or at the beginning) + if ( cur == text ) { + return cur; + } + if ( *cur == '\n' || *cur == '\r' ) { + return cur+1; + } + // otherwise maybe we're in commented out text or whatever, search on + res = strstr( res+1, findMe ); + } + return NULL; +} + void R_LoadARBProgram( int progIndex ) { int ofs; int err; @@ -409,6 +443,69 @@ void R_LoadARBProgram( int progIndex ) { } end[3] = 0; + // DG: hack gamma correction into shader + if ( r_gammaInShader.GetBool() && progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { + + // note that strlen("dhewm3tmpres") == strlen("result.color") + const char* tmpres = "TEMP dhewm3tmpres; # injected by dhewm3 for gamma correction\n"; + + // Note: program.env[4].xyz = r_brightness; program.env[4].w = 1.0/r_gamma + // outColor.rgb = pow(dhewm3tmpres.rgb*r_brightness, vec3(1.0/r_gamma)) + // outColor.a = dhewm3tmpres.a; + const char* extraLines = + "# gamma correction in shader, injected by dhewm3 \n" + "MUL dhewm3tmpres.xyz, program.env[4], dhewm3tmpres;\n" // first multiply with brightness + "POW result.color.x, dhewm3tmpres.x, program.env[4].w;\n" // then do pow(dhewm3tmpres.xyz, vec3(1/gamma)) + "POW result.color.y, dhewm3tmpres.y, program.env[4].w;\n" // (apparently POW only supports scalars, not whole vectors) + "POW result.color.z, dhewm3tmpres.z, program.env[4].w;\n" + "MOV result.color.w, dhewm3tmpres.w;\n" // alpha remains unmodified + "\nEND\n\n"; // we add this block right at the end, replacing the original "END" string + + int fullLen = strlen( start ) + strlen( tmpres ) + strlen( extraLines ); + char* outStr = (char*)_alloca( fullLen + 1 ); + + // add tmpres right after OPTION line (if any) + char* insertPos = findLineThatStartsWith( start, "OPTION" ); + if ( insertPos == NULL ) { + // no OPTION? then just put it after the first line (usually sth like "!!ARBfp1.0\n") + insertPos = start; + } + // but we want the position *after* that line + while( *insertPos != '\0' && *insertPos != '\n' && *insertPos != '\r' ) { + ++insertPos; + } + // skip the newline character(s) as well + while( *insertPos == '\n' || *insertPos == '\r' ) { + ++insertPos; + } + + // copy text up to insertPos + int curLen = insertPos-start; + memcpy( outStr, start, curLen ); + // copy tmpres ("TEMP dhewm3tmpres; # ..") + memcpy( outStr+curLen, tmpres, strlen( tmpres ) ); + curLen += strlen( tmpres ); + // copy remaining original shader up to (excluding) "END" + int remLen = end - insertPos; + memcpy( outStr+curLen, insertPos, remLen ); + curLen += remLen; + + outStr[curLen] = '\0'; // make sure it's NULL-terminated so normal string functions work + + // replace all existing occurrences of "result.color" with "dhewm3tmpres" + for( char* resCol = strstr( outStr, "result.color" ); + resCol != NULL; resCol = strstr( resCol+13, "result.color" ) ) { + memcpy( resCol, "dhewm3tmpres", 12 ); // both strings have the same length. + } + + assert( curLen + strlen( extraLines ) <= fullLen ); + + // now add extraLines that calculate and set a gamma-corrected result.color + // strcat() should be safe because fullLen was calculated taking all parts into account + strcat( outStr, extraLines ); + start = outStr; + } + qglBindProgramARB( progs[progIndex].target, progs[progIndex].ident ); qglGetError(); diff --git a/neo/renderer/draw_common.cpp b/neo/renderer/draw_common.cpp index b7932b86..06d50370 100644 --- a/neo/renderer/draw_common.cpp +++ b/neo/renderer/draw_common.cpp @@ -577,7 +577,7 @@ RB_SetProgramEnvironment Sets variables that can be used by all vertex programs ================== */ -void RB_SetProgramEnvironment( void ) { +void RB_SetProgramEnvironment( bool isPostProcess ) { float parm[4]; int pot; @@ -632,6 +632,20 @@ void RB_SetProgramEnvironment( void ) { parm[3] = 1; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm ); + // DG: brightness and gamma in shader as program.env[4] + if ( r_gammaInShader.GetBool() ) { + // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0/r_gamma + if ( !isPostProcess ) { + parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); + parm[3] = 1.0/r_gamma.GetFloat(); // 1.0/gamma so the shader doesn't have to do this calculation + } else { + // don't apply gamma/brightness in postprocess passes to avoid applying them twice + // (setting them to 1.0 makes them no-ops) + parm[0] = parm[1] = parm[2] = parm[3] = 1.0f; + } + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm ); + } + // // set eye position in global space // @@ -978,12 +992,15 @@ int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs ) { return numDrawSurfs; } + bool isPostProcess = false; + // if we are about to draw the first surface that needs // the rendering in a texture, copy it over if ( drawSurfs[0]->material->GetSort() >= SS_POST_PROCESS ) { if ( r_skipPostProcess.GetBool() ) { return 0; } + isPostProcess = true; // only dump if in a 3d view if ( backEnd.viewDef->viewEntitys && tr.backEndRenderer == BE_ARB2 ) { @@ -1000,7 +1017,7 @@ int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs ) { GL_SelectTexture( 0 ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - RB_SetProgramEnvironment(); + RB_SetProgramEnvironment( isPostProcess ); // we don't use RB_RenderDrawSurfListWithFunction() // because we want to defer the matrix load because many diff --git a/neo/renderer/tr_local.h b/neo/renderer/tr_local.h index e97ed83f..3828fc9c 100644 --- a/neo/renderer/tr_local.h +++ b/neo/renderer/tr_local.h @@ -837,6 +837,7 @@ extern idCVar r_flareSize; // scale the flare deforms from the material def extern idCVar r_gamma; // changes gamma tables extern idCVar r_brightness; // changes gamma tables +extern idCVar r_gammaInShader; // set gamma+brightness in shader instead of modifying system gamma tables extern idCVar r_renderer; // arb2, etc @@ -1092,6 +1093,8 @@ void GLimp_SetGamma( unsigned short red[256], // These are now taken as 16 bit values, so we can take full advantage // of dacs with >8 bits of precision +void GLimp_ResetGamma(); +// resets the gamma to what it was at startup // Returns false if the system only has a single processor diff --git a/neo/sys/glimp.cpp b/neo/sys/glimp.cpp index 16076d59..27dfc10b 100644 --- a/neo/sys/glimp.cpp +++ b/neo/sys/glimp.cpp @@ -540,6 +540,12 @@ void GLimp_SwapBuffers() { #endif } +static bool gammaOrigError = false; +static bool gammaOrigSet = false; +static unsigned short gammaOrigRed[256]; +static unsigned short gammaOrigGreen[256]; +static unsigned short gammaOrigBlue[256]; + /* ================= GLimp_SetGamma @@ -551,6 +557,19 @@ void GLimp_SetGamma(unsigned short red[256], unsigned short green[256], unsigned return; } + if ( !gammaOrigSet ) { + gammaOrigSet = true; +#if SDL_VERSION_ATLEAST(2, 0, 0) + if ( SDL_GetWindowGammaRamp( window, gammaOrigRed, gammaOrigGreen, gammaOrigBlue ) == -1 ) { +#else + if ( SDL_GetGammaRamp( gammaOrigRed, gammaOrigGreen, gammaOrigBlue ) == -1 ) { +#endif + gammaOrigError = true; + common->Warning( "Failed to get Gamma Ramp: %s\n", SDL_GetError() ); + } + } + + #if SDL_VERSION_ATLEAST(2, 0, 0) if (SDL_SetWindowGammaRamp(window, red, green, blue)) #else @@ -559,6 +578,30 @@ void GLimp_SetGamma(unsigned short red[256], unsigned short green[256], unsigned common->Warning("Couldn't set gamma ramp: %s", SDL_GetError()); } +/* +================= +GLimp_ResetGamma + +Restore original system gamma setting +================= +*/ +void GLimp_ResetGamma() { + if( gammaOrigError ) { + common->Warning( "Can't reset hardware gamma because getting the Gamma Ramp at startup failed!\n" ); + common->Warning( "You might have to restart the game for gamma/brightness in shaders to work properly.\n" ); + return; + } + + if( gammaOrigSet ) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_SetWindowGammaRamp( window, gammaOrigRed, gammaOrigGreen, gammaOrigBlue ); +#else + SDL_SetGammaRamp( gammaOrigRed, gammaOrigGreen, gammaOrigBlue ); +#endif + } +} + + /* ================= GLimp_ActivateContext diff --git a/neo/sys/stub/stub_gl.cpp b/neo/sys/stub/stub_gl.cpp index 1fb7dbdd..1296aeb0 100644 --- a/neo/sys/stub/stub_gl.cpp +++ b/neo/sys/stub/stub_gl.cpp @@ -389,6 +389,7 @@ GLExtension_t GLimp_ExtensionPointer( const char *a) { return StubFunction; }; bool GLimp_Init(glimpParms_t a) {return true;}; void GLimp_SetGamma(unsigned short*a, unsigned short*b, unsigned short*c) {}; +void GLimp_ResetGamma() {} bool GLimp_SetScreenParms(glimpParms_t parms) { return true; }; void GLimp_Shutdown() {}; void GLimp_SwapBuffers() {};