color_helpers: added default tonemapping for PQ->G22

This commit is contained in:
Jeremy Selan 2023-05-12 16:09:25 -07:00
parent 38e5b553fd
commit 6531d9cdf9
6 changed files with 117 additions and 38 deletions

View file

@ -660,7 +660,13 @@ inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping
return input; 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, void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
@ -751,6 +757,12 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
// Apply night mode // Apply night mode
destColorLinear = vNightModeMultLinear * destColorLinear * flGain; destColorLinear = vNightModeMultLinear * destColorLinear * flGain;
// Apply tonemapping
if ( tonemapping.bUseEEtf2390 )
{
destColorLinear = tonemapping.eetf2390.apply( destColorLinear );
}
// Apply dest EOTF // Apply dest EOTF
glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping ); glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping );

View file

@ -87,17 +87,26 @@ inline T nits_to_pq( const T& nits )
struct eetf_2390_t 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( init_pq(
nits_to_pq( sourceBlackNits ), nits_to_pq( sourceBlackNits ),
nits_to_pq( sourceWhiteNits ), nits_to_pq( sourceWhiteNits ),
nits_to_pq( targetBlackNits ), 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_sourceBlackPQ = sourceBlackPQ;
m_sourcePQScale = sourceWhitePQ - sourceBlackPQ; m_sourcePQScale = sourceWhitePQ - sourceBlackPQ;
m_invSourcePQScale = m_sourcePQScale > 0.f ? 1.f / m_sourcePQScale : 0.f; 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? 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" inline glm::vec3 apply( const glm::vec3 & inputNits ) const
// Digital Object Identifier 10.5594/JMI.2020.2984046
// Date of publication: 4 May 2020
inline glm::vec3 apply_max_rgb( const glm::vec3 & inputNits )
{ {
float input_scalar_nits = std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); switch ( m_eMode )
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; case RGBMode_Luma:
return inputNits * gain; 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; return pq_to_nits( apply_pq( nits_to_pq( inputNits ) ) );
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 )
{
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 // 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) // normalize PQ based on the mastering (source) display (E1)
float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale; float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale;
@ -158,7 +152,41 @@ struct eetf_2390_t
} }
private: 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 = ( value - ks ) / ( 1.f - ks ); // TODO : guard against ks == 1.f?
float t_sq = t*t; float t_sq = t*t;
@ -245,6 +273,8 @@ struct tonemapping_t
{ {
bool bUseShaper = true; bool bUseShaper = true;
float g22_luminance = 1.f; // what luminance should be applied for g22 EOTF conversions? float g22_luminance = 1.f; // what luminance should be applied for g22 EOTF conversions?
bool bUseEEtf2390 = false;
eetf_2390_t eetf2390;
}; };
struct lut1d_t struct lut1d_t

View file

@ -209,7 +209,7 @@ void test_eetf2390_mono()
printf("\n"); printf("\n");
eetf_2390_t eetf; 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 ) for ( size_t nLevel=0; nLevel < 12; ++nLevel )
{ {

View file

@ -375,6 +375,8 @@ struct gamescope_color_mgmt_t
displaycolorimetry_t outputEncodingColorimetry; displaycolorimetry_t outputEncodingColorimetry;
EOTF outputEncodingEOTF; EOTF outputEncodingEOTF;
std::shared_ptr<wlserver_hdr_metadata> appHDRMetadata = nullptr;
bool operator == (const gamescope_color_mgmt_t&) const = default; bool operator == (const gamescope_color_mgmt_t&) const = default;
bool operator != (const gamescope_color_mgmt_t&) const = default; bool operator != (const gamescope_color_mgmt_t&) const = default;
}; };

View file

@ -121,6 +121,7 @@ extern float g_flHDRItmSdrNits;
extern float g_flHDRItmTargetNits; extern float g_flHDRItmTargetNits;
extern std::atomic<uint64_t> g_lastVblank; extern std::atomic<uint64_t> g_lastVblank;
static uint32_t g_uTonemapDebug = 5;
uint64_t timespec_to_nanos(struct timespec& spec) uint64_t timespec_to_nanos(struct timespec& spec)
{ {
@ -218,6 +219,33 @@ update_color_mgmt()
{ {
// PQ -> G22 Leverage the display's native brightness // PQ -> G22 Leverage the display's native brightness
tonemapping.g22_luminance = g_ColorMgmt.pending.flInternalDisplayBrightness; 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 ); // xwm_log.infof("PQ -> 2.2 - tonemapping.g22_luminance %f", tonemapping.g22_luminance );
} }
else if ( g_ColorMgmt.pending.outputEncodingEOTF == EOTF_PQ ) 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 ) ) if ( set_color_nightmode( nightmode ) )
hasRepaint = true; 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 ) if ( ev->atom == ctx->atoms.gamescopeColorManagementDisable )
{ {
uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorManagementDisable, 0); 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.gamescopeHDRItmTargetNits = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_TARGET_NITS", false );
ctx->atoms.gamescopeColorLookPQ = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_PQ", 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.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 ); 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 ); bool app_wants_hdr = ColorspaceIsHDR( current_app_colorspace );
static bool s_bAppWantsHDRCached = false; static bool s_bAppWantsHDRCached = false;
static std::shared_ptr<wlserver_hdr_metadata> s_AppHDRMetadata = nullptr;
if ( app_wants_hdr != s_bAppWantsHDRCached ) if ( app_wants_hdr != s_bAppWantsHDRCached )
{ {
@ -6977,7 +7011,7 @@ steamcompmgr_main(int argc, char **argv)
flush_root = true; 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 )
{ {
@ -6994,7 +7028,7 @@ steamcompmgr_main(int argc, char **argv)
XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback); 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; flush_root = true;
} }
} }

View file

@ -186,6 +186,7 @@ struct xwayland_ctx_t
Atom gamescopeHDRItmTargetNits; Atom gamescopeHDRItmTargetNits;
Atom gamescopeColorLookPQ; Atom gamescopeColorLookPQ;
Atom gamescopeColorLookG22; Atom gamescopeColorLookG22;
Atom gamescopeTonemapDebug;
Atom gamescopeForceWindowsFullscreen; Atom gamescopeForceWindowsFullscreen;