diff --git a/src/color_bench.cpp b/src/color_bench.cpp index 0a8eeb1..bde6dd1 100644 --- a/src/color_bench.cpp +++ b/src/color_bench.cpp @@ -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(); iresize( 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 +#include +#include + /* 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; } \ No newline at end of file diff --git a/src/drm.cpp b/src/drm.cpp index 23fd6f0..eade865 100644 --- a/src/drm.cpp +++ b/src/drm.cpp @@ -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; } } diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 607264d..659d9cf 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -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 appHDRMetadata = nullptr; bool operator == (const gamescope_color_mgmt_t&) const = default; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 0f54771..c03938b 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -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( user_vec[0] ) ); + g_ColorMgmt.pending.outputVirtualWhite.y = santitize_float( bit_cast( 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 ); diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index 75a8d0e..f0f8eac 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -185,6 +185,7 @@ struct xwayland_ctx_t Atom gamescopeHDRItmTargetNits; Atom gamescopeColorLookPQ; Atom gamescopeColorLookG22; + Atom gamescopeColorOutputVirtualWhite; Atom gamescopeHDRTonemapDisplayMetadata; Atom gamescopeHDRTonemapSourceMetadata; Atom gamescopeHDRTonemapOperator;