diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index fe05defce6..4b24245960 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1479,6 +1479,8 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("searchTex"); mReservedUniforms.push_back("blendTex"); + mReservedUniforms.push_back("tonemapTex"); + llassert(mReservedUniforms.size() == END_RESERVED_UNIFORMS); std::set dupe_check; diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h index 015eac5861..d83522bcac 100644 --- a/indra/llrender/llshadermgr.h +++ b/indra/llrender/llshadermgr.h @@ -326,6 +326,8 @@ class LLShaderMgr SMAA_SEARCH_TEX, // "searchTex" SMAA_BLEND_TEX, // "blendTex" + TONEMAP_TEX, // "tonemapTex" + END_RESERVED_UNIFORMS } eGLSLReservedUniforms; // clang-format on diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 27275e676e..b2fa122d95 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10160,7 +10160,7 @@ RenderTonemapType Comment - What tonemapper to use: 0 = Khronos Neutral, 1 = ACES + What tonemapper to use: 0 = Khronos Neutral, 1 = ACES, 2 = AgX, 3 = Tony McMapface Persist 1 Type diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl index 9d449a5ae2..81da1e200e 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl @@ -30,6 +30,8 @@ out vec4 frag_color; uniform sampler2D diffuseRect; uniform sampler2D exposureMap; +uniform sampler2D tonemapTex; + uniform float gamma; uniform vec2 screen_res; in vec2 vary_fragcoord; @@ -120,6 +122,132 @@ vec3 PBRNeutralToneMapping( vec3 color ) return mix(color, newPeak * vec3(1, 1, 1), g); } +// https://github.com/godotengine/godot/blob/master/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl + +// Polynomial approximation of EaryChow's AgX sigmoid curve. +// x must be within the range [0.0, 1.0] +vec3 agx_contrast_approx(vec3 x) { + // Generated with Excel trendline + // Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps + // Additional padding values were added to give correct intersections at 0.0 and 1.0 + // 6th order, intercept of 0.0 to remove an operation and ensure intersection at 0.0 + vec3 x2 = x * x; + vec3 x4 = x2 * x2; + return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2; +} + +// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender. +// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses. +// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py +vec3 tonemap_agx(vec3 color) { + // Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices: + const mat3 srgb_to_rec2020_agx_inset_matrix = mat3( + 0.54490813676363087053, 0.14044005884001287035, 0.088827411851915368603, + 0.37377945959812267119, 0.75410959864013760045, 0.17887712465043811023, + 0.081384976686407536266, 0.10543358536857773485, 0.73224999956948382528); + + // Combined inverse AgX outset matrix and linear Rec 2020 to linear sRGB matrices. + const mat3 agx_outset_rec2020_to_srgb_matrix = mat3( + 1.9645509602733325934, -0.29932243390911083839, -0.16436833806080403409, + -0.85585845117807513559, 1.3264510741502356555, -0.23822464068860595117, + -0.10886710826831608324, -0.027084020983874825605, 1.402665347143271889); + + // LOG2_MIN = -10.0 + // LOG2_MAX = +6.5 + // MIDDLE_GRAY = 0.18 + const float min_ev = -12.4739311883324; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY) + const float max_ev = 4.02606881166759; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY) + + // Large negative values in one channel and large positive values in other + // channels can result in a colour that appears darker and more saturated than + // desired after passing it through the inset matrix. For this reason, it is + // best to prevent negative input values. + // This is done before the Rec. 2020 transform to allow the Rec. 2020 + // transform to be combined with the AgX inset matrix. This results in a loss + // of color information that could be correctly interpreted within the + // Rec. 2020 color space as positive RGB values, but it is less common for Godot + // to provide this function with negative sRGB values and therefore not worth + // the performance cost of an additional matrix multiplication. + // A value of 2e-10 intentionally introduces insignificant error to prevent + // log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after + // the matrix transform. + color = max(color, 2e-10); + + // Do AGX in rec2020 to match Blender and then apply inset matrix. + color = srgb_to_rec2020_agx_inset_matrix * color; + + // Log2 space encoding. + // Must be clamped because agx_contrast_approx may not work + // well with values outside of the range [0.0, 1.0] + color = clamp(log2(color), min_ev, max_ev); + color = (color - min_ev) / (max_ev - min_ev); + + // Apply sigmoid function approximation. + color = agx_contrast_approx(color); + + // Convert back to linear before applying outset matrix. + color = pow(color, vec3(2.4)); + + // Apply outset to make the result more chroma-laden and then go back to linear sRGB. + color = agx_outset_rec2020_to_srgb_matrix * color; + + // Blender's lusRGB.compensate_low_side is too complex for this shader, so + // simply return the color, even if it has negative components. These negative + // components may be useful for subsequent color adjustments. + return color; +} + +// https://github.com/h3r2tic/tony-mc-mapface/blob/main/shader/tony_mc_mapface.hlsl +// texture: https://github.com/SergeyMakeev/tony-mc-mapface-fuse +// godot pr: https://github.com/godotengine/godot/pull/97095 + +vec3 tony_mc_mapface(vec3 stimuli) { + // return textureLod(tonemapTex, vary_fragcoord, 0).rgb; + + vec3 encoded = stimuli / (stimuli + 1.0); + encoded = clamp(encoded, 0.0, 1.0); + + const float LUT_DIMS = 48.0; + vec3 uv = encoded * ((LUT_DIMS - 1.0) / LUT_DIMS) + 0.5 / LUT_DIMS; + + vec3 p = uv * LUT_DIMS; + + float iz = floor(p.z - 0.5); + float fz = p.z - 0.5 - iz; + + float x1 = iz * LUT_DIMS + p.x; + float x2 = (iz + 1.0) * LUT_DIMS + p.x; + float y = p.y; + + // const vec2 image_size = vec2(LUT_DIMS * LUT_DIMS, LUT_DIMS); + const vec2 image_size = vec2(512, 512); + const float split_every = 6.0; + const float row_height = 64.0; + + vec2 uv1 = vec2(x1, y); + uv1.y += floor(uv1.x / (LUT_DIMS * split_every)) * row_height; + uv1.x = mod(uv1.x, LUT_DIMS * split_every); + uv1 /= image_size; + + vec2 uv2 = vec2(x2, y); + uv2.y += floor(uv2.x / (LUT_DIMS * split_every)) * row_height; + uv2.x = mod(uv2.x, LUT_DIMS * split_every); + uv2 /= image_size; + + vec3 sp1 = textureLod(tonemapTex, uv1, 0).rgb; + vec3 sp2 = textureLod(tonemapTex, uv2, 0).rgb; + + vec3 linear = mix(sp1, sp2, fz); + return linear; + + // vec3 srgb = linear * 12.92; + // if (linear.r > 0.0031308) srgb.r = 1.055 * pow(linear.r, 1.0 / 2.4) - 0.055; + // if (linear.g > 0.0031308) srgb.g = 1.055 * pow(linear.g, 1.0 / 2.4) - 0.055; + // if (linear.b > 0.0031308) srgb.b = 1.055 * pow(linear.b, 1.0 / 2.4) - 0.055; + + // return srgb; +} + uniform float exposure; uniform float tonemap_mix; uniform int tonemap_type; @@ -141,6 +269,12 @@ vec3 toneMap(vec3 color) case 1: color = toneMapACES_Hill(color); break; + case 2: + color = tonemap_agx(color); + break; + case 3: + color = tony_mc_mapface(color); + break; } // mix tonemapped and linear here to provide adjustment diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 389f6c2068..1af9063ac2 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -80,6 +80,8 @@ LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap; LLTexturePipelineTester* LLViewerTextureManager::sTesterp = nullptr; F32 LLViewerFetchedTexture::sMaxVirtualSize = 8192.f*8192.f; +LLPointer LLViewerFetchedTexture::sTonemapImagep = nullptr; + const std::string sTesterName("TextureTester"); S32 LLViewerTexture::sImageCount = 0; @@ -463,6 +465,8 @@ void LLViewerTextureManager::cleanup() LLViewerFetchedTexture::sFlatNormalImagep = NULL; LLViewerFetchedTexture::sDefaultIrradiancePBRp = NULL; + LLViewerFetchedTexture::sTonemapImagep = NULL; + LLViewerMediaTexture::cleanUpClass(); } diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index dec8adfce6..fe05bc2023 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -515,6 +515,8 @@ class LLViewerFetchedTexture : public LLViewerTexture // not sure why, but something is iffy about the loading of this particular texture, use the accessor instead of accessing directly static LLPointer sSmokeImagep; // Old "Default" translucent texture static LLViewerFetchedTexture* getSmokeImage(); + + static LLPointer sTonemapImagep; }; // diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index fdf0d7f5ca..78b54497c3 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -191,6 +191,15 @@ void LLViewerTextureList::doPreloadImages() image->setAddressMode(LLTexUnit::TAM_CLAMP); mImagePreloads.insert(image); } + + LLViewerFetchedTexture::sTonemapImagep = LLViewerTextureManager::getFetchedTextureFromFile( + "TonyMcMapfaceLUTPacked.png", FTT_LOCAL_FILE, MIPMAP_NO, + LLViewerFetchedTexture::BOOST_UI + ); + + LLViewerFetchedTexture::sTonemapImagep->setFilteringOption(LLTexUnit::TFO_TRILINEAR); + LLViewerFetchedTexture::sTonemapImagep->mGLTexturep->setAllowCompression(false); + } static std::string get_texture_list_name() diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 78cba27ce3..665eba1b76 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -7179,6 +7179,10 @@ void LLPipeline::tonemap(LLRenderTarget* src, LLRenderTarget* dst, bool gamma_co shader->uniform1i(tonemap_type, tonemap_type_setting); shader->uniform1f(tonemap_mix, psky->getTonemapMix(should_auto_adjust())); + if (tonemap_type_setting() == 3) { + shader->bindTexture(LLShaderMgr::TONEMAP_TEX, LLViewerFetchedTexture::sTonemapImagep); + } + mScreenTriangleVB->setBuffer(); mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); diff --git a/indra/newview/skins/default/textures/TonyMcMapfaceLUTPacked.png b/indra/newview/skins/default/textures/TonyMcMapfaceLUTPacked.png new file mode 100644 index 0000000000..c5ba805d2f Binary files /dev/null and b/indra/newview/skins/default/textures/TonyMcMapfaceLUTPacked.png differ diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml index 0a9d88c6d1..6fcc869741 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml @@ -1035,6 +1035,14 @@ label="ACES" name="1" value="1"/> + +