[color]: Added support for overriding the apparent (virtual) white point
Also, updated the steamdeck colorimetry to measured values
This commit is contained in:
parent
f2e925f5d6
commit
da12ca6b76
8 changed files with 113 additions and 30 deletions
|
|
@ -15,6 +15,7 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state)
|
|||
{
|
||||
const primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } };
|
||||
const glm::vec2 white = { 0.3070f, 0.3220f };
|
||||
const glm::vec2 destVirtualWhite = { 0.f, 0.f };
|
||||
|
||||
displaycolorimetry_t inputColorimetry{};
|
||||
inputColorimetry.primaries = primaries;
|
||||
|
|
@ -35,6 +36,7 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state)
|
|||
for (auto _ : state) {
|
||||
calcColorTransform( &lut1d_float, nLutSize1d, &lut3d_float, nLutEdgeSize3d, inputColorimetry, inputEOTF,
|
||||
outputEncodingColorimetry, EOTF_Gamma22,
|
||||
destVirtualWhite, k_EChromaticAdapatationMethod_XYZ,
|
||||
colorMapping, nightmode, tonemapping, nullptr, flGain );
|
||||
for ( size_t i=0, end = lut1d_float.dataR.size(); i<end; ++i )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -75,18 +75,13 @@ glm::mat3 normalised_primary_matrix( const primaries_t & rgbPrimaries, const glm
|
|||
return matPrimaries * whiteScale;
|
||||
}
|
||||
|
||||
enum EChromaticAdaptationMethod
|
||||
{
|
||||
k_EChromaticAdapatationMethod_XYZ,
|
||||
k_EChromaticAdapatationMethod_Bradford,
|
||||
};
|
||||
|
||||
glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhite, const glm::vec3 & destWhite, EChromaticAdaptationMethod eMethod )
|
||||
glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhiteXYZ, const glm::vec3 & destWhiteXYZ,
|
||||
EChromaticAdaptationMethod eMethod )
|
||||
{
|
||||
static const glm::mat3 k_matBradford( 0.8951f,-0.7502f, 0.0389f, 0.2664f,1.7135f, -0.0685f, -0.1614f, 0.0367f, 1.0296f );
|
||||
glm::mat3 matAdaptation = eMethod == k_EChromaticAdapatationMethod_XYZ ? glm::diagonal3x3( glm::vec3(1,1,1) ) : k_matBradford;
|
||||
glm::vec3 coneResponseDest = matAdaptation * destWhite;
|
||||
glm::vec3 coneResponseSource = matAdaptation * sourceWhite;
|
||||
glm::vec3 coneResponseDest = matAdaptation * destWhiteXYZ;
|
||||
glm::vec3 coneResponseSource = matAdaptation * sourceWhiteXYZ;
|
||||
glm::vec3 scale = glm::vec3( coneResponseDest.x / coneResponseSource.x, coneResponseDest.y / coneResponseSource.y, coneResponseDest.z / coneResponseSource.z );
|
||||
return glm::inverse( matAdaptation ) * glm::diagonal3x3( scale ) * matAdaptation;
|
||||
}
|
||||
|
|
@ -670,14 +665,10 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
|
|||
lut3d_t * pLut3d, int nLutEdgeSize3d,
|
||||
const displaycolorimetry_t & source, EOTF sourceEOTF,
|
||||
const displaycolorimetry_t & dest, EOTF destEOTF,
|
||||
const glm::vec2 & destVirtualWhite, EChromaticAdaptationMethod eMethod,
|
||||
const colormapping_t & mapping, const nightmode_t & nightmode, const tonemapping_t & tonemapping,
|
||||
const lut3d_t * pLook, float flGain )
|
||||
{
|
||||
glm::mat3 xyz_from_dest = normalised_primary_matrix( dest.primaries, dest.white, 1.f );
|
||||
glm::mat3 dest_from_xyz = glm::inverse( xyz_from_dest );
|
||||
glm::mat3 xyz_from_source = normalised_primary_matrix( source.primaries, source.white, 1.f );
|
||||
glm::mat3 dest_from_source = dest_from_xyz * xyz_from_source; // Absolute colorimetric mapping
|
||||
|
||||
// Generate shaper lut
|
||||
// Note: while this is typically a 1D approximation of our end to end transform,
|
||||
// it need not be! Conceptually this is just to determine the interpolation properties...
|
||||
|
|
@ -703,28 +694,54 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
|
|||
|
||||
if ( pLut3d )
|
||||
{
|
||||
pLut3d->resize( nLutEdgeSize3d );
|
||||
glm::mat3 xyz_from_dest = normalised_primary_matrix( dest.primaries, dest.white, 1.f );
|
||||
glm::mat3 dest_from_xyz = glm::inverse( xyz_from_dest );
|
||||
|
||||
float flScale = 1.f / ( (float) nLutEdgeSize3d - 1.f );
|
||||
glm::mat3 xyz_from_source = normalised_primary_matrix( source.primaries, source.white, 1.f );
|
||||
glm::mat3 dest_from_source = dest_from_xyz * xyz_from_source; // XYZ scaling for white point adjustment
|
||||
|
||||
// Precalc night mode scalars
|
||||
// Precalc night mode scalars & digital gain
|
||||
// amount and saturation are overdetermined but we separate the two as they conceptually represent
|
||||
// different quantities, and this preserves forwards algorithmic compatibility
|
||||
glm::vec3 nightModeMultHSV( nightmode.hue, clamp01( nightmode.saturation * nightmode.amount ), 1.f );
|
||||
glm::vec3 vNightModeMultLinear = glm::pow( hsv_to_rgb( nightModeMultHSV ), glm::vec3( 2.2f ) );
|
||||
glm::vec3 vMultLinear = glm::pow( hsv_to_rgb( nightModeMultHSV ), glm::vec3( 2.2f ) );
|
||||
vMultLinear = vMultLinear * flGain;
|
||||
|
||||
glm::vec3 vSourceColorEOTFEncodedEdge[nLutEdgeSize3d];
|
||||
// Calculate the virtual white point adaptation
|
||||
glm::mat3x3 whitePointDestAdaptation = glm::mat3x3( 1.f ); // identity
|
||||
if ( destVirtualWhite.x != 0.f && destVirtualWhite.y != 0.f )
|
||||
{
|
||||
// if source white is within tiny tolerance of sourceWhitePointOverride
|
||||
// don't do the override? (aka two quantizations of d65)
|
||||
glm::mat3x3 virtualWhiteXYZFromPhysicalWhiteXYZ = chromatic_adaptation_matrix(
|
||||
xy_to_xyz( dest.white ), xy_to_xyz( destVirtualWhite ), k_EChromaticAdapatationMethod_Bradford );
|
||||
whitePointDestAdaptation = dest_from_xyz * virtualWhiteXYZFromPhysicalWhiteXYZ * xyz_from_dest;
|
||||
|
||||
bool bLimitGain = true;
|
||||
if ( bLimitGain )
|
||||
{
|
||||
glm::vec3 white = whitePointDestAdaptation * glm::vec3(1.f, 1.f, 1.f );
|
||||
float whiteMax = std::max( white.r, std::max( white.g, white.b ) );
|
||||
float normScale = 1.f / whiteMax;
|
||||
fprintf( stderr, "normScale %f\n", normScale );
|
||||
whitePointDestAdaptation = whitePointDestAdaptation * glm::diagonal3x3( glm::vec3( normScale ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Precalculate source color EOTF encoded per-edge.
|
||||
glm::vec3 vSourceColorEOTFEncodedEdge[nLutEdgeSize3d];
|
||||
float flEdgeScale = 1.f / ( (float) nLutEdgeSize3d - 1.f );
|
||||
for ( int nIndex = 0; nIndex < nLutEdgeSize3d; ++nIndex )
|
||||
{
|
||||
vSourceColorEOTFEncodedEdge[nIndex] = glm::vec3( nIndex * flScale );
|
||||
vSourceColorEOTFEncodedEdge[nIndex] = glm::vec3( nIndex * flEdgeScale );
|
||||
if ( pShaper )
|
||||
{
|
||||
vSourceColorEOTFEncodedEdge[nIndex] = ApplyLut1D_Inverse_Linear( *pShaper, vSourceColorEOTFEncodedEdge[nIndex] );
|
||||
}
|
||||
}
|
||||
|
||||
pLut3d->resize( nLutEdgeSize3d );
|
||||
|
||||
for ( int nBlue=0; nBlue<nLutEdgeSize3d; ++nBlue )
|
||||
{
|
||||
for ( int nGreen=0; nGreen<nLutEdgeSize3d; ++nGreen )
|
||||
|
|
@ -751,8 +768,11 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
|
|||
float amount = cfit( colorSaturation, mapping.blendEnableMinSat, mapping.blendEnableMaxSat, mapping.blendAmountMin, mapping.blendAmountMax );
|
||||
destColorLinear = glm::mix( destColorLinear, sourceColorLinear, amount );
|
||||
|
||||
// Apply night mode
|
||||
destColorLinear = vNightModeMultLinear * destColorLinear * flGain;
|
||||
// Apply linear Mult
|
||||
destColorLinear = vMultLinear * destColorLinear;
|
||||
|
||||
// Apply destination virtual white point mapping
|
||||
destColorLinear = whitePointDestAdaptation * destColorLinear;
|
||||
|
||||
// Apply tonemapping
|
||||
destColorLinear = tonemapping.apply( destColorLinear );
|
||||
|
|
@ -783,6 +803,9 @@ void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *p
|
|||
if (flSDRGamutWideness < 0 )
|
||||
flSDRGamutWideness = 1.0f;
|
||||
|
||||
displaycolorimetry_t r709NativeWhite = displaycolorimetry_709;
|
||||
r709NativeWhite.white = nativeDisplayOutput.white;
|
||||
|
||||
// 0.0: 709
|
||||
// 1.0: Native
|
||||
colormapping_t noRemap;
|
||||
|
|
@ -791,7 +814,7 @@ void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *p
|
|||
noRemap.blendAmountMin = 0.0f;
|
||||
noRemap.blendAmountMax = 0.0f;
|
||||
*pMapping = noRemap;
|
||||
*pColorimetry = lerp( displaycolorimetry_709, nativeDisplayOutput, flSDRGamutWideness );
|
||||
*pColorimetry = lerp( r709NativeWhite, nativeDisplayOutput, flSDRGamutWideness );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -820,16 +843,19 @@ void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *p
|
|||
partialRemap.blendAmountMin = 0.0f;
|
||||
partialRemap.blendAmountMax = 0.25;
|
||||
|
||||
displaycolorimetry_t wideGamutNativeWhite = displaycolorimetry_widegamutgeneric;
|
||||
wideGamutNativeWhite.white = nativeDisplayOutput.white;
|
||||
|
||||
if ( flSDRGamutWideness < 0.5f )
|
||||
{
|
||||
float t = cfit( flSDRGamutWideness, 0.f, 0.5f, 0.0f, 1.0f );
|
||||
*pColorimetry = lerp( nativeDisplayOutput, displaycolorimetry_widegamutgeneric, t );
|
||||
*pColorimetry = lerp( nativeDisplayOutput, wideGamutNativeWhite, t );
|
||||
*pMapping = smoothRemap;
|
||||
}
|
||||
else
|
||||
{
|
||||
float t = cfit( flSDRGamutWideness, 0.5f, 1.0f, 0.0f, 1.0f );
|
||||
*pColorimetry = displaycolorimetry_widegamutgeneric;
|
||||
*pColorimetry = wideGamutNativeWhite;
|
||||
*pMapping = lerp( smoothRemap, partialRemap, t );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -299,6 +299,16 @@ struct tonemapping_t
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
enum EChromaticAdaptationMethod
|
||||
{
|
||||
k_EChromaticAdapatationMethod_XYZ,
|
||||
k_EChromaticAdapatationMethod_Bradford,
|
||||
};
|
||||
|
||||
glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhiteXYZ, const glm::vec3 & destWhiteXYZ,
|
||||
EChromaticAdaptationMethod eMethod );
|
||||
|
||||
struct lut1d_t
|
||||
{
|
||||
int lutSize = 0;
|
||||
|
|
@ -354,10 +364,13 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d,
|
|||
lut3d_t * pLut3d, int nLutEdgeSize3d,
|
||||
const displaycolorimetry_t & source, EOTF sourceEOTF,
|
||||
const displaycolorimetry_t & dest, EOTF destEOTF,
|
||||
const glm::vec2 & destVirtualWhite, EChromaticAdaptationMethod eMethod,
|
||||
const colormapping_t & mapping, const nightmode_t & nightmode, const tonemapping_t & tonemapping,
|
||||
const lut3d_t * pLook, float flGain );
|
||||
|
||||
// Build colorimetry and a gamut mapping for the given SDR configuration
|
||||
// Note: the output colorimetry will use the native display's white point
|
||||
// Only the color gamut will change
|
||||
void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pMapping,
|
||||
float flSDRGamutWideness, const displaycolorimetry_t & nativeDisplayOutput );
|
||||
|
||||
|
|
@ -411,12 +424,18 @@ nits_to_u16_dark(float nits)
|
|||
return (uint16_t)round(nits * 10000.0f);
|
||||
}
|
||||
|
||||
static constexpr displaycolorimetry_t displaycolorimetry_steamdeck
|
||||
static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_spec
|
||||
{
|
||||
.primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } },
|
||||
.white = { 0.3070f, 0.3220f }, // not D65
|
||||
};
|
||||
|
||||
static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_measured
|
||||
{
|
||||
.primaries = { { 0.603f, 0.349f }, { 0.335f, 0.571f }, { 0.163f, 0.115f } },
|
||||
.white = { 0.296f, 0.307f }, // not D65
|
||||
};
|
||||
|
||||
static constexpr displaycolorimetry_t displaycolorimetry_709
|
||||
{
|
||||
.primaries = { { 0.64f, 0.33f }, { 0.30f, 0.60f }, { 0.15f, 0.06f } },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include "color_helpers.h"
|
||||
#include <cstdio>
|
||||
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
|
||||
/*
|
||||
const uint32_t nLutSize1d = 4096;
|
||||
const uint32_t nLutEdgeSize3d = 17;
|
||||
|
|
@ -76,13 +79,19 @@ int color_tests()
|
|||
#endif
|
||||
|
||||
|
||||
#if 0
|
||||
#if 1
|
||||
{
|
||||
// chromatic adapatation
|
||||
glm::vec3 d50XYZ = glm::vec3(0.96422f, 1.00000f, 0.82521f );
|
||||
glm::vec3 d65XYZ = glm::vec3(0.95047f, 1.00000f, 1.08883f );
|
||||
printf("d50XYZ %s\n", glm::to_string(d50XYZ).c_str() );
|
||||
printf("d65XYZ %s\n", glm::to_string(d65XYZ).c_str() );
|
||||
|
||||
glm::mat3x3 d65FromF50_reference_bradford( 0.9555766, -0.0282895, 0.0122982,
|
||||
-0.0230393, 1.0099416, -0.0204830,
|
||||
0.0631636, 0.0210077, 1.3299098 );
|
||||
printf("d65FromF50_reference_bradford %s\n", glm::to_string(d65FromF50_reference_bradford).c_str() );
|
||||
|
||||
{
|
||||
|
||||
glm::mat3x3 d65From50 = chromatic_adaptation_matrix( d50XYZ, d65XYZ, k_EChromaticAdapatationMethod_Bradford );
|
||||
|
|
@ -91,12 +100,12 @@ int color_tests()
|
|||
printf("bradford d65_2 %s\n", glm::to_string(d65_2).c_str() );
|
||||
}
|
||||
{
|
||||
|
||||
glm::mat3x3 d65From50 = chromatic_adaptation_matrix( d50XYZ, d65XYZ, k_EChromaticAdapatationMethod_XYZ );
|
||||
printf("xyzscaling d65From50 %s\n", glm::to_string(d65From50).c_str() );
|
||||
glm::vec3 d65_2 = d65From50 * d50XYZ;
|
||||
printf("xyzscaling d65_2 %s\n", glm::to_string(d65_2).c_str() );
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -224,6 +233,7 @@ void test_eetf2390_mono()
|
|||
int main(int argc, char* argv[])
|
||||
{
|
||||
printf("color_tests\n");
|
||||
test_eetf2390_mono();
|
||||
// test_eetf2390_mono();
|
||||
color_tests();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -454,7 +454,7 @@ drm_hdr_parse_edid(drm_t *drm, struct connector *connector, const struct di_edid
|
|||
// Hardcode Steam Deck display info to support
|
||||
// BIOSes with missing info for this in EDID.
|
||||
drm_log.infof("[colorimetry]: using default steamdeck colorimetry");
|
||||
metadata->colorimetry = displaycolorimetry_steamdeck;
|
||||
metadata->colorimetry = displaycolorimetry_steamdeck_measured;
|
||||
metadata->eotf = EOTF_Gamma22;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -384,6 +384,9 @@ struct gamescope_color_mgmt_t
|
|||
displaycolorimetry_t outputEncodingColorimetry;
|
||||
EOTF outputEncodingEOTF;
|
||||
|
||||
// If non-zero, use this as the emulated "virtual" white point for the output
|
||||
glm::vec2 outputVirtualWhite = { 0.f, 0.f };
|
||||
|
||||
std::shared_ptr<wlserver_hdr_metadata> appHDRMetadata = nullptr;
|
||||
|
||||
bool operator == (const gamescope_color_mgmt_t&) const = default;
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_col
|
|||
|
||||
calcColorTransform( &g_tmpLut1d, s_nLutSize1d, &g_tmpLut3d, s_nLutEdgeSize3d, inputColorimetry, inputEOTF,
|
||||
outputEncodingColorimetry, newColorMgmt.outputEncodingEOTF,
|
||||
newColorMgmt.outputVirtualWhite, k_EChromaticAdapatationMethod_XYZ,
|
||||
colorMapping, newColorMgmt.nightmode, tonemapping, pLook, flGain );
|
||||
|
||||
// Create quantized output luts
|
||||
|
|
@ -4823,6 +4824,11 @@ steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx )
|
|||
}
|
||||
}
|
||||
|
||||
static inline float santitize_float( float f )
|
||||
{
|
||||
return ( std::isfinite( f ) ? f : 0.f );
|
||||
}
|
||||
|
||||
static void
|
||||
handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
|
||||
{
|
||||
|
|
@ -5359,6 +5365,21 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
|
|||
if ( set_color_look_g22( path.c_str() ) )
|
||||
hasRepaint = true;
|
||||
}
|
||||
if ( ev->atom == ctx->atoms.gamescopeColorOutputVirtualWhite )
|
||||
{
|
||||
std::vector< uint32_t > user_vec;
|
||||
if ( get_prop( ctx, ctx->root, ctx->atoms.gamescopeColorOutputVirtualWhite, user_vec ) && user_vec.size() >= 2 )
|
||||
{
|
||||
g_ColorMgmt.pending.outputVirtualWhite.x = santitize_float( bit_cast<float>( user_vec[0] ) );
|
||||
g_ColorMgmt.pending.outputVirtualWhite.y = santitize_float( bit_cast<float>( user_vec[1] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ColorMgmt.pending.outputVirtualWhite.x = 0.f;
|
||||
g_ColorMgmt.pending.outputVirtualWhite.y = 0.f;
|
||||
}
|
||||
hasRepaint = true;
|
||||
}
|
||||
if ( ev->atom == ctx->atoms.gamescopeInternalDisplayBrightness )
|
||||
{
|
||||
uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeInternalDisplayBrightness, 0 );
|
||||
|
|
@ -6628,6 +6649,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.gamescopeColorOutputVirtualWhite = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_VIRTUAL_WHITE", false );
|
||||
ctx->atoms.gamescopeHDRTonemapDisplayMetadata = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_DISPLAY_METADATA", false );
|
||||
ctx->atoms.gamescopeHDRTonemapSourceMetadata = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_SOURCE_METADATA", false );
|
||||
ctx->atoms.gamescopeHDRTonemapOperator = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_OPERATOR", false );
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ struct xwayland_ctx_t
|
|||
Atom gamescopeHDRItmTargetNits;
|
||||
Atom gamescopeColorLookPQ;
|
||||
Atom gamescopeColorLookG22;
|
||||
Atom gamescopeColorOutputVirtualWhite;
|
||||
Atom gamescopeHDRTonemapDisplayMetadata;
|
||||
Atom gamescopeHDRTonemapSourceMetadata;
|
||||
Atom gamescopeHDRTonemapOperator;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue