From 6531d9cdf9f4e360dd293ca24f03a20515c3512a Mon Sep 17 00:00:00 2001 From: Jeremy Selan Date: Fri, 12 May 2023 16:09:25 -0700 Subject: [PATCH] color_helpers: added default tonemapping for PQ->G22 --- src/color_helpers.cpp | 14 ++++++- src/color_helpers.h | 94 ++++++++++++++++++++++++++++--------------- src/color_tests.cpp | 2 +- src/rendervulkan.hpp | 2 + src/steamcompmgr.cpp | 42 +++++++++++++++++-- src/xwayland_ctx.hpp | 1 + 6 files changed, 117 insertions(+), 38 deletions(-) diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp index 06568a8..1d223fe 100644 --- a/src/color_helpers.cpp +++ b/src/color_helpers.cpp @@ -660,7 +660,13 @@ inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping return input; } - return calcLinearToEOTF( flGain * calcEOTFToLinear( input, source, tonemapping ), dest, tonemapping ); + T flLinear = flGain * calcEOTFToLinear( input, source, tonemapping ); + if ( tonemapping.bUseEEtf2390 ) + { + flLinear = tonemapping.eetf2390.apply( flLinear ); + } + + return calcLinearToEOTF( flLinear, dest, tonemapping ); } void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, @@ -751,6 +757,12 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, // Apply night mode destColorLinear = vNightModeMultLinear * destColorLinear * flGain; + // Apply tonemapping + if ( tonemapping.bUseEEtf2390 ) + { + destColorLinear = tonemapping.eetf2390.apply( destColorLinear ); + } + // Apply dest EOTF glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping ); diff --git a/src/color_helpers.h b/src/color_helpers.h index c081305..d62cf3e 100644 --- a/src/color_helpers.h +++ b/src/color_helpers.h @@ -87,17 +87,26 @@ inline T nits_to_pq( const T& nits ) struct eetf_2390_t { - void init_nits( float sourceBlackNits, float sourceWhiteNits, float targetBlackNits, float targetWhiteNits ) + enum RGBMode + { + RGBMode_Max, + RGBMode_Luma, + RGBMode_Independent, + }; + + void init_nits( float sourceBlackNits, float sourceWhiteNits, float targetBlackNits, float targetWhiteNits, RGBMode eMode ) { init_pq( nits_to_pq( sourceBlackNits ), nits_to_pq( sourceWhiteNits ), nits_to_pq( targetBlackNits ), - nits_to_pq( targetWhiteNits ) ); + nits_to_pq( targetWhiteNits ), + eMode ); } - void init_pq( float sourceBlackPQ, float sourceWhitePQ, float targetBlackPQ, float targetWhitePQ ) + void init_pq( float sourceBlackPQ, float sourceWhitePQ, float targetBlackPQ, float targetWhitePQ, RGBMode eMode ) { + m_eMode = eMode; m_sourceBlackPQ = sourceBlackPQ; m_sourcePQScale = sourceWhitePQ - sourceBlackPQ; m_invSourcePQScale = m_sourcePQScale > 0.f ? 1.f / m_sourcePQScale : 0.f; @@ -106,41 +115,26 @@ struct eetf_2390_t m_ks = 1.5 * m_maxLumPQ - 0.5; // TODO : return false if ks == 1.f? } - // "Max RGB" approach, as defined in "Color Volume and Hue-Preservation in HDR Tone Mapping" - // Digital Object Identifier 10.5594/JMI.2020.2984046 - // Date of publication: 4 May 2020 - inline glm::vec3 apply_max_rgb( const glm::vec3 & inputNits ) + inline glm::vec3 apply( const glm::vec3 & inputNits ) const { - float input_scalar_nits = std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); - float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); - float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; - return inputNits * gain; + switch ( m_eMode ) + { + case RGBMode_Luma: + return apply_luma_rgb( inputNits ); + case RGBMode_Independent: + return apply_independent_rgb( inputNits ); + default: + return apply_max_rgb( inputNits ); + } } - inline glm::vec3 apply_luma_rgb( const glm::vec3 & inputNits ) + inline float apply( float inputNits ) const { - float input_scalar_nits = 0.2627f * inputNits.r + 0.6780f * inputNits.g + 0.0593f * inputNits.b; - float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); - float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; - return inputNits * gain; + return pq_to_nits( apply_pq( nits_to_pq( inputNits ) ) ); } - inline glm::vec3 apply_independent_rgb( const glm::vec3 & inputNits ) - { - glm::vec3 inputPQ = nits_to_pq( inputNits ); - glm::vec3 outputPQ = { apply_pq( inputPQ.r ), apply_pq( inputPQ.g ), apply_pq( inputPQ.b ) }; - return pq_to_nits( outputPQ ); - } - - float m_sourceBlackPQ = 0.f; - float m_sourcePQScale = 0.f; - float m_invSourcePQScale = 0.f; - float m_minLumPQ = 0.f; - float m_maxLumPQ = 0.f; - float m_ks = 0.f; - // Raw PQ transfer function - inline float apply_pq( float valuePQ ) + inline float apply_pq( float valuePQ ) const { // normalize PQ based on the mastering (source) display (E1) float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale; @@ -158,7 +152,41 @@ struct eetf_2390_t } private: - inline float _eetf_2390_spline( float value, float ks, float maxLum ) + RGBMode m_eMode = RGBMode_Max; + float m_sourceBlackPQ = 0.f; + float m_sourcePQScale = 0.f; + float m_invSourcePQScale = 0.f; + float m_minLumPQ = 0.f; + float m_maxLumPQ = 0.f; + float m_ks = 0.f; + + // "Max RGB" approach, as defined in "Color Volume and Hue-Preservation in HDR Tone Mapping" + // Digital Object Identifier 10.5594/JMI.2020.2984046 + // Date of publication: 4 May 2020 + inline glm::vec3 apply_max_rgb( const glm::vec3 & inputNits ) const + { + float input_scalar_nits = std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); + float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; + return inputNits * gain; + } + + inline glm::vec3 apply_luma_rgb( const glm::vec3 & inputNits ) const + { + float input_scalar_nits = 0.2627f * inputNits.r + 0.6780f * inputNits.g + 0.0593f * inputNits.b; + float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; + return inputNits * gain; + } + + inline glm::vec3 apply_independent_rgb( const glm::vec3 & inputNits ) const + { + glm::vec3 inputPQ = nits_to_pq( inputNits ); + glm::vec3 outputPQ = { apply_pq( inputPQ.r ), apply_pq( inputPQ.g ), apply_pq( inputPQ.b ) }; + return pq_to_nits( outputPQ ); + } + + inline float _eetf_2390_spline( float value, float ks, float maxLum ) const { float t = ( value - ks ) / ( 1.f - ks ); // TODO : guard against ks == 1.f? float t_sq = t*t; @@ -245,6 +273,8 @@ struct tonemapping_t { bool bUseShaper = true; float g22_luminance = 1.f; // what luminance should be applied for g22 EOTF conversions? + bool bUseEEtf2390 = false; + eetf_2390_t eetf2390; }; struct lut1d_t diff --git a/src/color_tests.cpp b/src/color_tests.cpp index 51e5dab..6713765 100644 --- a/src/color_tests.cpp +++ b/src/color_tests.cpp @@ -209,7 +209,7 @@ void test_eetf2390_mono() printf("\n"); eetf_2390_t eetf; - eetf.init_pq( sourceBlackPQ, sourceWhitePQ, destBlackPQ, destWhitePQ ); + eetf.init_pq( sourceBlackPQ, sourceWhitePQ, destBlackPQ, destWhitePQ, eetf_2390_t::RGBMode_Max ); for ( size_t nLevel=0; nLevel < 12; ++nLevel ) { diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 011459d..85a8261 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -375,6 +375,8 @@ struct gamescope_color_mgmt_t displaycolorimetry_t outputEncodingColorimetry; EOTF outputEncodingEOTF; + std::shared_ptr appHDRMetadata = nullptr; + bool operator == (const gamescope_color_mgmt_t&) const = default; bool operator != (const gamescope_color_mgmt_t&) const = default; }; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 80d40ca..9a2e3a0 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -121,6 +121,7 @@ extern float g_flHDRItmSdrNits; extern float g_flHDRItmTargetNits; extern std::atomic g_lastVblank; +static uint32_t g_uTonemapDebug = 5; uint64_t timespec_to_nanos(struct timespec& spec) { @@ -218,6 +219,33 @@ update_color_mgmt() { // PQ -> G22 Leverage the display's native brightness tonemapping.g22_luminance = g_ColorMgmt.pending.flInternalDisplayBrightness; + + if ( g_uTonemapDebug == 1 ) + { + tonemapping.bUseEEtf2390 = true; + tonemapping.eetf2390.init_nits( 0.001f, 5000.f, 0.01f, 1000.f, eetf_2390_t::RGBMode_Max ); + } + else if ( g_uTonemapDebug == 2 ) + { + tonemapping.bUseEEtf2390 = true; + tonemapping.eetf2390.init_nits( 0.001f, 2000.f, 0.01f, 1000.f, eetf_2390_t::RGBMode_Luma ); + } + else if ( g_uTonemapDebug == 3 ) + { + tonemapping.bUseEEtf2390 = true; + tonemapping.eetf2390.init_nits( 0.001f, 5000.f, 0.1f, 1000.f, eetf_2390_t::RGBMode_Luma ); + } + else if ( g_uTonemapDebug == 4 ) + { + tonemapping.bUseEEtf2390 = true; + tonemapping.eetf2390.init_nits( 0.0f, 5000.f, 0.1f, 1000.f, eetf_2390_t::RGBMode_Luma ); + } + else if ( g_uTonemapDebug == 5 ) + { + tonemapping.bUseEEtf2390 = true; + tonemapping.eetf2390.init_nits( 0.001f, 5000.f, 0.5f, 1000.f, eetf_2390_t::RGBMode_Luma ); + } + // xwm_log.infof("PQ -> 2.2 - tonemapping.g22_luminance %f", tonemapping.g22_luminance ); } else if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_PQ ) @@ -5131,6 +5159,12 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if ( set_color_nightmode( nightmode ) ) hasRepaint = true; } + if ( ev->atom == ctx->atoms.gamescopeTonemapDebug ) + { + g_uTonemapDebug = get_prop( ctx, ctx->root, ctx->atoms.gamescopeTonemapDebug, 0 ); + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; + } if ( ev->atom == ctx->atoms.gamescopeColorManagementDisable ) { uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorManagementDisable, 0); @@ -6325,6 +6359,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ ctx->atoms.gamescopeHDRItmTargetNits = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_TARGET_NITS", false ); ctx->atoms.gamescopeColorLookPQ = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_PQ", false ); ctx->atoms.gamescopeColorLookG22 = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_G22", false ); + ctx->atoms.gamescopeTonemapDebug = XInternAtom( ctx->dpy, "GAMESCOPE_TONEMAP_DEBUG", false ); ctx->atoms.gamescopeForceWindowsFullscreen = XInternAtom( ctx->dpy, "GAMESCOPE_FORCE_WINDOWS_FULLSCREEN", false ); @@ -6964,7 +6999,6 @@ steamcompmgr_main(int argc, char **argv) bool app_wants_hdr = ColorspaceIsHDR( current_app_colorspace ); static bool s_bAppWantsHDRCached = false; - static std::shared_ptr s_AppHDRMetadata = nullptr; if ( app_wants_hdr != s_bAppWantsHDRCached ) { @@ -6977,9 +7011,9 @@ steamcompmgr_main(int argc, char **argv) flush_root = true; } - if ( app_hdr_metadata != s_AppHDRMetadata ) + if ( app_hdr_metadata != g_ColorMgmt.pending.appHDRMetadata ) { - if (app_hdr_metadata) + if ( app_hdr_metadata ) { std::vector app_hdr_metadata_blob; app_hdr_metadata_blob.resize((sizeof(hdr_metadata_infoframe) + (sizeof(uint32_t) - 1)) / sizeof(uint32_t)); @@ -6994,7 +7028,7 @@ steamcompmgr_main(int argc, char **argv) XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback); } - s_AppHDRMetadata = app_hdr_metadata; + g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata; flush_root = true; } } diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index 2f47c26..929cc4b 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -186,6 +186,7 @@ struct xwayland_ctx_t Atom gamescopeHDRItmTargetNits; Atom gamescopeColorLookPQ; Atom gamescopeColorLookG22; + Atom gamescopeTonemapDebug; Atom gamescopeForceWindowsFullscreen;