From 8c97cb7858e5d6c35d1a055930904346fb4248db Mon Sep 17 00:00:00 2001 From: UjinT34 <41110182+UjinT34@users.noreply.github.com> Date: Fri, 14 Mar 2025 02:15:18 +0300 Subject: [PATCH] renderer: add simple color management (#9506) Adds proper color management and transformations for CM surfaces. --- src/config/ConfigDescriptions.hpp | 18 +- src/config/ConfigManager.cpp | 29 +- src/helpers/Monitor.cpp | 64 +++- src/helpers/Monitor.hpp | 37 +- src/protocols/ColorManagement.cpp | 73 ++-- src/protocols/XXColorManagement.cpp | 45 ++- src/protocols/types/ColorManagement.hpp | 35 +- src/render/OpenGL.cpp | 98 +++++- src/render/OpenGL.hpp | 52 +-- src/render/Renderer.cpp | 95 ++--- src/render/Shader.hpp | 11 + src/render/pass/SurfacePassElement.cpp | 2 + src/render/shaders/CM.frag | 439 ++++++++++++++++++++++++ src/render/shaders/Textures.hpp | 4 + 14 files changed, 823 insertions(+), 179 deletions(-) create mode 100644 src/render/shaders/CM.frag diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 7049fa2fb..b007129d7 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1378,6 +1378,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{true}, }, + SConfigOptionDescription{ + .value = "render:cm_fs_passthrough", + .description = "Passthrough color settings for fullscreen apps when possible", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, /* * cursor: @@ -1764,18 +1770,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{true}, }, - SConfigOptionDescription{ - .value = "experimental:wide_color_gamut", - .description = "force wide color gamut for all supported outputs", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "experimental:hdr", - .description = "force static hdr for all supported outputs", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, SConfigOptionDescription{ .value = "experimental:xx_color_management_v4", .description = "enable color management protocol", diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 2bd793e51..eda5c7332 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -692,12 +692,11 @@ CConfigManager::CConfigManager() { registerConfigVar("render:xp_mode", Hyprlang::INT{0}); registerConfigVar("render:ctm_animation", Hyprlang::INT{2}); registerConfigVar("render:allow_early_buffer_release", Hyprlang::INT{1}); + registerConfigVar("render:cm_fs_passthrough", Hyprlang::INT{1}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); - registerConfigVar("experimental:wide_color_gamut", Hyprlang::INT{0}); - registerConfigVar("experimental:hdr", Hyprlang::INT{0}); registerConfigVar("experimental:xx_color_management_v4", Hyprlang::INT{0}); // devices @@ -2042,6 +2041,32 @@ std::optional CConfigManager::handleMonitor(const std::string& comm } else if (ARGS[argno] == "bitdepth") { newrule.enable10bit = ARGS[argno + 1] == "10"; argno++; + } else if (ARGS[argno] == "cm") { + if (ARGS[argno + 1] == "auto") + newrule.cmType = CM_AUTO; + else if (ARGS[argno + 1] == "srgb") + newrule.cmType = CM_SRGB; + else if (ARGS[argno + 1] == "wide") + newrule.cmType = CM_WIDE; + else if (ARGS[argno + 1] == "edid") + newrule.cmType = CM_EDID; + else if (ARGS[argno + 1] == "hdr") + newrule.cmType = CM_HDR; + else if (ARGS[argno + 1] == "hdredid") + newrule.cmType = CM_HDR_EDID; + else + error = "invalid cm "; + argno++; + } else if (ARGS[argno] == "sdrsaturation") { + try { + newrule.sdrSaturation = stof(ARGS[argno + 1]); + } catch (...) { error = "invalid sdrsaturation "; } + argno++; + } else if (ARGS[argno] == "sdrbrightness") { + try { + newrule.sdrBrightness = stof(ARGS[argno + 1]); + } catch (...) { error = "invalid sdrbrightness "; } + argno++; } else if (ARGS[argno] == "transform") { if (!isNumber(ARGS[argno + 1])) { error = "invalid transform "; diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index df672caf3..ce30b8e79 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2,6 +2,7 @@ #include "MiscFunctions.hpp" #include "../macros.hpp" #include "math/Math.hpp" +#include "../protocols/ColorManagement.hpp" #include "sync/SyncReleaser.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" @@ -412,7 +413,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (!force && DELTALESSTHAN(vecPixelSize.x, RULE->resolution.x, 1) && DELTALESSTHAN(vecPixelSize.y, RULE->resolution.y, 1) && DELTALESSTHAN(refreshRate, RULE->refreshRate, 1) && setScale == RULE->scale && ((DELTALESSTHAN(vecPosition.x, RULE->offset.x, 1) && DELTALESSTHAN(vecPosition.y, RULE->offset.y, 1)) || RULE->offset == Vector2D(-INT32_MAX, -INT32_MAX)) && - transform == RULE->transform && RULE->enable10bit == enabled10bit && !std::memcmp(&customDrmMode, &RULE->drmMode, sizeof(customDrmMode))) { + transform == RULE->transform && RULE->enable10bit == enabled10bit && RULE->cmType == cmType && RULE->sdrSaturation == sdrSaturation && + RULE->sdrBrightness == sdrBrightness && !std::memcmp(&customDrmMode, &RULE->drmMode, sizeof(customDrmMode))) { Debug::log(LOG, "Not applying a new rule to {} because it's already applied!", szName); @@ -670,6 +672,66 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { enabled10bit = set10bit; + auto oldImageDescription = imageDescription; + cmType = RULE->cmType; + switch (cmType) { + case CM_AUTO: cmType = enabled10bit && output->parsedEDID.supportsBT2020 ? CM_WIDE : CM_SRGB; break; + case CM_EDID: cmType = output->parsedEDID.chromaticityCoords.has_value() ? CM_EDID : CM_SRGB; break; + case CM_HDR: + case CM_HDR_EDID: + cmType = output->parsedEDID.supportsBT2020 && output->parsedEDID.hdrMetadata.has_value() && output->parsedEDID.hdrMetadata->supportsPQ ? cmType : CM_SRGB; + break; + default: break; + } + switch (cmType) { + case CM_SRGB: imageDescription = {}; break; // assumes SImageDescirption defaults to sRGB + case CM_WIDE: + imageDescription = {.primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)}; + break; + case CM_EDID: + imageDescription = {.primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = { + .red = {.x = output->parsedEDID.chromaticityCoords->red.x, .y = output->parsedEDID.chromaticityCoords->red.y}, + .green = {.x = output->parsedEDID.chromaticityCoords->green.x, .y = output->parsedEDID.chromaticityCoords->green.y}, + .blue = {.x = output->parsedEDID.chromaticityCoords->blue.x, .y = output->parsedEDID.chromaticityCoords->blue.y}, + .white = {.x = output->parsedEDID.chromaticityCoords->white.x, .y = output->parsedEDID.chromaticityCoords->white.y}, + }}; + break; + case CM_HDR: + imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .luminances = {.min = 0, .max = 10000, .reference = 203}}; + break; + case CM_HDR_EDID: + imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = output->parsedEDID.chromaticityCoords.has_value() ? + NColorManagement::SPCPRimaries{ + .red = {.x = output->parsedEDID.chromaticityCoords->red.x, .y = output->parsedEDID.chromaticityCoords->red.y}, + .green = {.x = output->parsedEDID.chromaticityCoords->green.x, .y = output->parsedEDID.chromaticityCoords->green.y}, + .blue = {.x = output->parsedEDID.chromaticityCoords->blue.x, .y = output->parsedEDID.chromaticityCoords->blue.y}, + .white = {.x = output->parsedEDID.chromaticityCoords->white.x, .y = output->parsedEDID.chromaticityCoords->white.y}, + } : + NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .luminances = {.min = output->parsedEDID.hdrMetadata->desiredContentMinLuminance, + .max = output->parsedEDID.hdrMetadata->desiredContentMaxLuminance, + .reference = output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}; + + break; + default: UNREACHABLE(); + } + if (oldImageDescription != imageDescription) + PROTO::colorManagement->onMonitorImageDescriptionChanged(self); + + sdrSaturation = RULE->sdrSaturation; + sdrBrightness = RULE->sdrBrightness; + Vector2D logicalSize = vecPixelSize / scale; if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { // invalid scale, will produce fractional pixels. diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 9850e31e7..10e807a05 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -28,18 +28,30 @@ enum eAutoDirs : uint8_t { DIR_AUTO_RIGHT }; +enum eCMType : uint8_t { + CM_AUTO = 0, // subject to change. srgb for 8bpc, wide for 10bpc if supported + CM_SRGB, // default, sRGB primaries + CM_WIDE, // wide color gamut, BT2020 primaries + CM_EDID, // primaries from edid (known to be inaccurate) + CM_HDR, // wide color gamut and HDR PQ transfer function + CM_HDR_EDID, // same as CM_HDR with edid primaries +}; + struct SMonitorRule { - eAutoDirs autoDir = DIR_AUTO_NONE; - std::string name = ""; - Vector2D resolution = Vector2D(1280, 720); - Vector2D offset = Vector2D(0, 0); - float scale = 1; - float refreshRate = 60; // Hz - bool disabled = false; - wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; - std::string mirrorOf = ""; - bool enable10bit = false; - drmModeModeInfo drmMode = {}; + eAutoDirs autoDir = DIR_AUTO_NONE; + std::string name = ""; + Vector2D resolution = Vector2D(1280, 720); + Vector2D offset = Vector2D(0, 0); + float scale = 1; + float refreshRate = 60; // Hz + bool disabled = false; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + std::string mirrorOf = ""; + bool enable10bit = false; + eCMType cmType = CM_SRGB; + float sdrSaturation = 1.0f; // SDR -> HDR + float sdrBrightness = 1.0f; // SDR -> HDR + drmModeModeInfo drmMode = {}; std::optional vrr; }; @@ -108,6 +120,9 @@ class CMonitor { bool dpmsStatus = true; bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. + eCMType cmType = CM_SRGB; + float sdrSaturation = 1.0f; + float sdrBrightness = 1.0f; bool createdByUser = false; bool isUnsafeFallback = false; diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index 1a95c8bff..b6aee17b3 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -14,46 +14,47 @@ CColorManager::CColorManager(SP resource) : m_resource(resour if UNLIKELY (!good()) return; + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC); + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES); + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES); + if (PROTO::colorManagement->m_debug) { m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); } m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_SRGB); - if (PROTO::colorManagement->m_debug) { - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_BT2020); // HDR for fullscreen only + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_BT2020); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_NTSC); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_PAL); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_NTSC); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM); + if (PROTO::colorManagement->m_debug) { m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3); - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB); } m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB); - if (PROTO::colorManagement->m_debug) { - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ); // HDR for fullscreen only + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28); + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ); + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG); + if (PROTO::colorManagement->m_debug) { m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG); } m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); @@ -162,11 +163,6 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->setCreateParametricCreator([](CWpColorManagerV1* r, uint32_t id) { LOGM(TRACE, "New parametric creator for id={}", id); - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Parametric creator is not supported"); - return; - } - const auto RESOURCE = PROTO::colorManagement->m_vParametricCreators.emplace_back( makeShared(makeShared(r->client(), r->version(), id))); @@ -536,6 +532,10 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, "Unsupported transfer function"); return; } @@ -566,11 +566,6 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPm_debug && primaries != WP_COLOR_MANAGER_V1_PRIMARIES_SRGB) { - r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, "Unsupported primaries"); - return; - } - switch (primaries) { case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: @@ -580,14 +575,18 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, "Unsupported primaries"); + case WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB: break; + default: + if (!PROTO::colorManagement->m_debug) { + r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, "Unsupported primaries"); + return; + } } + + settings.primariesNameSet = true; + settings.primariesNamed = convertPrimaries((wpColorManagerV1Primaries)primaries); + settings.primaries = getPrimaries(settings.primariesNamed); + valuesSet |= PC_PRIMARIES; }); m_resource->setSetPrimaries( [this](CWpImageDescriptionCreatorParamsV1* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { @@ -618,10 +617,6 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, "Invalid luminances"); return; } - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Luminances aren't supported"); - return; - } settings.luminances = SImageDescription::SPCLuminances{.min = min, .max = max_lum, .reference = reference_lum}; valuesSet |= PC_LUMINANCES; }); diff --git a/src/protocols/XXColorManagement.cpp b/src/protocols/XXColorManagement.cpp index 57d4c5fd3..36ace0f2d 100644 --- a/src/protocols/XXColorManagement.cpp +++ b/src/protocols/XXColorManagement.cpp @@ -3,6 +3,7 @@ #include "ColorManagement.hpp" #include "color-management-v1.hpp" #include "types/ColorManagement.hpp" +#include "xx-color-management-v4.hpp" using namespace NColorManagement; @@ -40,20 +41,22 @@ CXXColorManager::CXXColorManager(SP resource_) : resource(res resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_SET_LUMINANCES); resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_SRGB); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL_M); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_NTSC); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_GENERIC_FILM); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL_M); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_NTSC); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_GENERIC_FILM); resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_BT2020); // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_CIE1931_XYZ); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DCI_P3); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DISPLAY_P3); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_ADOBE_RGB); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DCI_P3); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DISPLAY_P3); + resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_ADOBE_RGB); - // resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22); + resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22); + resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA28); + resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_HLG); resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_SRGB); resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST2084_PQ); - // resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR); + resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR); resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_PERCEPTUAL); // resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_RELATIVE); @@ -396,8 +399,12 @@ CXXColorManagementParametricCreator::CXXColorManagementParametricCreator(SPerror(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_TF, "Unsupported transfer function"); return; } @@ -422,15 +429,17 @@ CXXColorManagementParametricCreator::CXXColorManagementParametricCreator(SPerror(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_PRIMARIES, "Unsupported primaries"); diff --git a/src/protocols/types/ColorManagement.hpp b/src/protocols/types/ColorManagement.hpp index 8c0643c1d..143f47ca7 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/protocols/types/ColorManagement.hpp @@ -48,10 +48,17 @@ namespace NColorManagement { } struct SPCPRimaries { - struct { + struct xy { //NOLINT(readability-identifier-naming) float x = 0; float y = 0; + + bool operator==(const xy& p2) const { + return x == p2.x && y == p2.y; + } } red, green, blue, white; + bool operator==(const SPCPRimaries& p2) const { + return red == p2.red && green == p2.green && blue == p2.blue && white == p2.white; + } }; namespace NColorPrimaries { @@ -102,6 +109,7 @@ namespace NColorManagement { .blue = {.x = 0.150, .y = 0.060}, .white = {.x = 0.314, .y = 0.351}, }; + static const auto DISPLAY_P3 = SPCPRimaries{ .red = {.x = 0.680, .y = 0.320}, .green = {.x = 0.265, .y = 0.690}, @@ -125,6 +133,9 @@ namespace NColorManagement { int fd = -1; uint32_t length = 0; uint32_t offset = 0; + bool operator==(const SIccFile& i2) const { + return fd == i2.fd; + } } icc; bool windowsScRGB = false; @@ -135,7 +146,8 @@ namespace NColorManagement { bool primariesNameSet = false; ePrimaries primariesNamed = CM_PRIMARIES_SRGB; // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0) - // wayland protocol expects int32_t values multiplied by 10000 + // wayland protocol expects int32_t values multiplied by 1000000 + // xx protocol expects int32_t values multiplied by 10000 // drm expects uint16_t values multiplied by 50000 // frog protocol expects drm values SPCPRimaries primaries, masteringPrimaries; @@ -146,13 +158,32 @@ namespace NColorManagement { float min = 0.2; // 0.2 cd/m² uint32_t max = 80; // 80 cd/m² uint32_t reference = 80; // 80 cd/m² + bool operator==(const SPCLuminances& l2) const { + return min == l2.min && max == l2.max && reference == l2.reference; + } } luminances; struct SPCMasteringLuminances { float min = 0; uint32_t max = 0; + bool operator==(const SPCMasteringLuminances& l2) const { + return min == l2.min && max == l2.max; + } } masteringLuminances; uint32_t maxCLL = 0; uint32_t maxFALL = 0; + + bool operator==(const SImageDescription& d2) const { + return (id != 0 && id == d2.id) || + (icc == d2.icc && windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && + ((primariesNameSet && primariesNamed == d2.primariesNameSet) || (primaries == d2.primaries)) && masteringPrimaries == d2.masteringPrimaries && + luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL && maxFALL == d2.maxFALL); + } + + const SPCPRimaries& getPrimaries() const { + if (primariesNameSet || primaries == SPCPRimaries{}) + return NColorManagement::getPrimaries(primariesNamed); + return primaries; + } }; } \ No newline at end of file diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 17224523b..a8a38bac2 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -9,6 +9,7 @@ #include "../desktop/LayerSurface.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/ColorManagement.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/input/InputManager.hpp" #include "pass/TexPassElement.hpp" @@ -20,6 +21,7 @@ #include #include using namespace Hyprutils::OS; +using namespace NColorManagement; const std::vector ASSET_PATHS = { #ifdef DATAROOTDIR @@ -866,6 +868,39 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shQUAD.radius = glGetUniformLocation(prog, "radius"); m_RenderData.pCurrentMonData->m_shQUAD.roundingPower = glGetUniformLocation(prog, "roundingPower"); +#ifndef GLES2 + prog = createProgram(TEXVERTSRC320, TEXFRAGSRCCM); + m_RenderData.pCurrentMonData->m_shCM.program = prog; + m_RenderData.pCurrentMonData->m_shCM.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shCM.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shCM.texType = glGetUniformLocation(prog, "texType"); + m_RenderData.pCurrentMonData->m_shCM.skipCM = glGetUniformLocation(prog, "skipCM"); + m_RenderData.pCurrentMonData->m_shCM.sourceTF = glGetUniformLocation(prog, "sourceTF"); + m_RenderData.pCurrentMonData->m_shCM.targetTF = glGetUniformLocation(prog, "targetTF"); + m_RenderData.pCurrentMonData->m_shCM.sourcePrimaries = glGetUniformLocation(prog, "sourcePrimaries"); + m_RenderData.pCurrentMonData->m_shCM.targetPrimaries = glGetUniformLocation(prog, "targetPrimaries"); + m_RenderData.pCurrentMonData->m_shCM.maxLuminance = glGetUniformLocation(prog, "maxLuminance"); + m_RenderData.pCurrentMonData->m_shCM.dstMaxLuminance = glGetUniformLocation(prog, "dstMaxLuminance"); + m_RenderData.pCurrentMonData->m_shCM.dstRefLuminance = glGetUniformLocation(prog, "dstRefLuminance"); + m_RenderData.pCurrentMonData->m_shCM.sdrSaturation = glGetUniformLocation(prog, "sdrSaturation"); + m_RenderData.pCurrentMonData->m_shCM.sdrBrightness = glGetUniformLocation(prog, "sdrBrightnessMultiplier"); + m_RenderData.pCurrentMonData->m_shCM.alphaMatte = glGetUniformLocation(prog, "texMatte"); + m_RenderData.pCurrentMonData->m_shCM.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shCM.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shCM.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); + m_RenderData.pCurrentMonData->m_shCM.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shCM.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + m_RenderData.pCurrentMonData->m_shCM.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + m_RenderData.pCurrentMonData->m_shCM.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + m_RenderData.pCurrentMonData->m_shCM.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shCM.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shCM.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shCM.roundingPower = glGetUniformLocation(prog, "roundingPower"); + m_RenderData.pCurrentMonData->m_shCM.applyTint = glGetUniformLocation(prog, "applyTint"); + m_RenderData.pCurrentMonData->m_shCM.tint = glGetUniformLocation(prog, "tint"); + m_RenderData.pCurrentMonData->m_shCM.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); +#endif + prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA); m_RenderData.pCurrentMonData->m_shRGBA.program = prog; m_RenderData.pCurrentMonData->m_shRGBA.proj = glGetUniformLocation(prog, "proj"); @@ -1280,7 +1315,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, const CB CBox newBox = box; m_RenderData.renderModif.applyToBox(newBox); - static auto PDT = CConfigValue("debug:damage_tracking"); + static auto PDT = CConfigValue("debug:damage_tracking"); + static auto PPASS = CConfigValue("render:cm_fs_passthrough"); // get the needed transform for this texture const bool TRANSFORMS_MATCH = wlTransformToHyprutils(m_RenderData.pMonitor->transform) == tex->m_eTransform; // FIXME: combine them properly!!! @@ -1304,6 +1340,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, const CB const bool CRASHING = m_bApplyFinalShader && g_pHyprRenderer->m_bCrashingInProgress; + auto texType = tex->m_iType; + if (CRASHING) { shader = &m_RenderData.pCurrentMonData->m_shGLITCH; usingFinalShader = true; @@ -1316,16 +1354,27 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, const CB usingFinalShader = true; } else { switch (tex->m_iType) { +#ifdef GLES2 case TEXTURE_RGBA: shader = &m_RenderData.pCurrentMonData->m_shRGBA; break; case TEXTURE_RGBX: shader = &m_RenderData.pCurrentMonData->m_shRGBX; break; - case TEXTURE_EXTERNAL: shader = &m_RenderData.pCurrentMonData->m_shEXT; break; +#else + case TEXTURE_RGBA: + case TEXTURE_RGBX: shader = &m_RenderData.pCurrentMonData->m_shCM; break; +#endif + case TEXTURE_EXTERNAL: shader = &m_RenderData.pCurrentMonData->m_shEXT; break; // might be unused default: RASSERT(false, "tex->m_iTarget unsupported!"); } } } - if (m_RenderData.currentWindow && m_RenderData.currentWindow->m_sWindowData.RGBX.valueOrDefault()) + if (m_RenderData.currentWindow && m_RenderData.currentWindow->m_sWindowData.RGBX.valueOrDefault()) { +#ifdef GLES2 shader = &m_RenderData.pCurrentMonData->m_shRGBX; +#else + shader = &m_RenderData.pCurrentMonData->m_shCM; +#endif + texType = TEXTURE_RGBX; + } glActiveTexture(GL_TEXTURE0); glBindTexture(tex->m_iTarget, tex->m_iTexID); @@ -1350,6 +1399,49 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, const CB glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1i(shader->tex, 0); +#ifndef GLES2 + if (!usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) { + const bool skipCM = *PPASS && m_RenderData.pMonitor->activeWorkspace && m_RenderData.pMonitor->activeWorkspace->m_bHasFullscreenWindow && + m_RenderData.pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN; + glUniform1i(shader->texType, texType); + glUniform1i(shader->skipCM, skipCM); + if (!skipCM) { + const auto imageDescription = + m_RenderData.surface.valid() && m_RenderData.surface->colorManagement.valid() ? m_RenderData.surface->colorManagement->imageDescription() : SImageDescription{}; + glUniform1i(shader->sourceTF, imageDescription.transferFunction); + glUniform1i(shader->targetTF, m_RenderData.pMonitor->imageDescription.transferFunction); + const auto sourcePrimaries = + imageDescription.primariesNameSet || imageDescription.primaries == SPCPRimaries{} ? getPrimaries(imageDescription.primariesNamed) : imageDescription.primaries; + const auto targetPrimaries = m_RenderData.pMonitor->imageDescription.primariesNameSet || m_RenderData.pMonitor->imageDescription.primaries == SPCPRimaries{} ? + getPrimaries(m_RenderData.pMonitor->imageDescription.primariesNamed) : + m_RenderData.pMonitor->imageDescription.primaries; + + const GLfloat glSourcePrimaries[8] = { + sourcePrimaries.red.x, sourcePrimaries.red.y, sourcePrimaries.green.x, sourcePrimaries.green.y, + sourcePrimaries.blue.x, sourcePrimaries.blue.y, sourcePrimaries.white.x, sourcePrimaries.white.y, + }; + const GLfloat glTargetPrimaries[8] = { + targetPrimaries.red.x, targetPrimaries.red.y, targetPrimaries.green.x, targetPrimaries.green.y, + targetPrimaries.blue.x, targetPrimaries.blue.y, targetPrimaries.white.x, targetPrimaries.white.y, + }; + glUniformMatrix4x2fv(shader->sourcePrimaries, 1, false, glSourcePrimaries); + glUniformMatrix4x2fv(shader->targetPrimaries, 1, false, glTargetPrimaries); + + const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference; + glUniform1f(shader->maxLuminance, maxLuminance * m_RenderData.pMonitor->imageDescription.luminances.reference / imageDescription.luminances.reference); + glUniform1f(shader->dstMaxLuminance, m_RenderData.pMonitor->imageDescription.luminances.max > 0 ? m_RenderData.pMonitor->imageDescription.luminances.max : 10000); + glUniform1f(shader->dstRefLuminance, m_RenderData.pMonitor->imageDescription.luminances.reference); + glUniform1f(shader->sdrSaturation, + m_RenderData.pMonitor->sdrSaturation > 0 && m_RenderData.pMonitor->imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_RenderData.pMonitor->sdrSaturation : + 1.0f); + glUniform1f(shader->sdrBrightness, + m_RenderData.pMonitor->sdrBrightness > 0 && m_RenderData.pMonitor->imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_RenderData.pMonitor->sdrBrightness : + 1.0f); + } + } +#endif if ((usingFinalShader && *PDT == 0) || CRASHING) { glUniform1f(shader->time, m_tGlobalTimer.getSeconds() - shader->initialTime); diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index cf1d25490..110795918 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -27,6 +27,7 @@ #include #include "../debug/TracyDefines.hpp" +#include "../protocols/core/Compositor.hpp" struct gbm_device; class CHyprRenderer; @@ -105,41 +106,42 @@ struct SMonitorRenderData { CShader m_shSHADOW; CShader m_shBORDER1; CShader m_shGLITCH; - // + CShader m_shCM; }; struct SCurrentRenderData { - PHLMONITORREF pMonitor; - Mat3x3 projection; - Mat3x3 savedProjection; - Mat3x3 monitorProjection; + PHLMONITORREF pMonitor; + Mat3x3 projection; + Mat3x3 savedProjection; + Mat3x3 monitorProjection; - SMonitorRenderData* pCurrentMonData = nullptr; - CFramebuffer* currentFB = nullptr; // current rendering to - CFramebuffer* mainFB = nullptr; // main to render to - CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) + SMonitorRenderData* pCurrentMonData = nullptr; + CFramebuffer* currentFB = nullptr; // current rendering to + CFramebuffer* mainFB = nullptr; // main to render to + CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) - CRegion damage; - CRegion finalDamage; // damage used for funal off -> main + CRegion damage; + CRegion finalDamage; // damage used for funal off -> main - SRenderModifData renderModif; - float mouseZoomFactor = 1.f; - bool mouseZoomUseMouse = true; // true by default - bool useNearestNeighbor = false; - bool blockScreenShader = false; - bool simplePass = false; + SRenderModifData renderModif; + float mouseZoomFactor = 1.f; + bool mouseZoomUseMouse = true; // true by default + bool useNearestNeighbor = false; + bool blockScreenShader = false; + bool simplePass = false; - Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); - Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); + Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); + Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); - CBox clipBox = {}; // scaled coordinates - CRegion clipRegion; + CBox clipBox = {}; // scaled coordinates + CRegion clipRegion; - uint32_t discardMode = DISCARD_OPAQUE; - float discardOpacity = 0.f; + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; - PHLLSREF currentLS; - PHLWINDOWREF currentWindow; + PHLLSREF currentLS; + PHLWINDOWREF currentWindow; + WP surface; }; class CEGLSync { diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 3175b915f..88de1adab 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -32,9 +32,9 @@ #include "pass/RendererHintsPassElement.hpp" #include "pass/SurfacePassElement.hpp" #include "debug/Log.hpp" -#include "protocols/ColorManagement.hpp" +#include "../protocols/ColorManagement.hpp" #if AQUAMARINE_VERSION_NUMBER > 702 // >0.7.2 -#include "protocols/types/ContentType.hpp" +#include "../protocols/types/ContentType.hpp" #endif #include @@ -1409,78 +1409,40 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor) { } } -static const auto BT709 = Aquamarine::IOutput::SChromaticityCoords{ - .red = Aquamarine::IOutput::xy{.x = 0.64, .y = 0.33}, - .green = Aquamarine::IOutput::xy{.x = 0.30, .y = 0.60}, - .blue = Aquamarine::IOutput::xy{.x = 0.15, .y = 0.06}, - .white = Aquamarine::IOutput::xy{.x = 0.3127, .y = 0.3290}, -}; +static const hdr_output_metadata NO_HDR_METADATA = {.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; -static hdr_output_metadata createHDRMetadata(uint8_t eotf, Aquamarine::IOutput::SParsedEDID edid) { - if (eotf == 0) - return hdr_output_metadata{.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; // empty metadata for SDR - - const auto toNits = [](float value) { return uint16_t(std::round(value)); }; - const auto to16Bit = [](float value) { return uint16_t(std::round(value * 50000)); }; - const auto colorimetry = edid.chromaticityCoords.value_or(BT709); - - Debug::log(TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, colorimetry.blue.x, - colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); - Debug::log(TRACE, "ColorManagement max avg {}, min {}, max {}", edid.hdrMetadata->desiredMaxFrameAverageLuminance, edid.hdrMetadata->desiredContentMinLuminance, - edid.hdrMetadata->desiredContentMaxLuminance); - return hdr_output_metadata{ - .metadata_type = 0, - .hdmi_metadata_type1 = - hdr_metadata_infoframe{ - .eotf = eotf, - .metadata_type = 0, - .display_primaries = - { - {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)}, - {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)}, - {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)}, - }, - .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, - .max_display_mastering_luminance = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - .min_display_mastering_luminance = toNits(edid.hdrMetadata->desiredContentMinLuminance * 10000), - .max_cll = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - .max_fall = toNits(edid.hdrMetadata->desiredMaxFrameAverageLuminance), - }, - }; -} - -static hdr_output_metadata createHDRMetadata(SImageDescription settings, Aquamarine::IOutput::SParsedEDID edid) { +static hdr_output_metadata createHDRMetadata(SImageDescription settings, Aquamarine::IOutput::SParsedEDID edid) { if (settings.transferFunction != CM_TRANSFER_FUNCTION_ST2084_PQ) - return hdr_output_metadata{.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}}; // empty metadata for SDR + return NO_HDR_METADATA; // empty metadata for SDR const auto toNits = [](uint32_t value) { return uint16_t(std::round(value)); }; const auto to16Bit = [](uint32_t value) { return uint16_t(std::round(value * 50000)); }; - auto colorimetry = settings.primaries; + auto colorimetry = settings.primariesNameSet || settings.primaries == SPCPRimaries{} ? getPrimaries(settings.primariesNamed) : settings.primaries; auto luminances = settings.masteringLuminances.max > 0 ? - settings.masteringLuminances : - SImageDescription::SPCMasteringLuminances{.min = edid.hdrMetadata->desiredContentMinLuminance, .max = edid.hdrMetadata->desiredContentMaxLuminance}; + settings.masteringLuminances : + SImageDescription::SPCMasteringLuminances{.min = edid.hdrMetadata->desiredContentMinLuminance, .max = edid.hdrMetadata->desiredContentMaxLuminance}; Debug::log(TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, colorimetry.blue.x, - colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); + colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); Debug::log(TRACE, "ColorManagement min {}, max {}, cll {}, fall {}", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL); return hdr_output_metadata{ - .metadata_type = 0, - .hdmi_metadata_type1 = + .metadata_type = 0, + .hdmi_metadata_type1 = hdr_metadata_infoframe{ - .eotf = 2, - .metadata_type = 0, - .display_primaries = - { + .eotf = 2, + .metadata_type = 0, + .display_primaries = + { {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)}, {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)}, {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)}, }, - .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, - .max_display_mastering_luminance = toNits(luminances.max), - .min_display_mastering_luminance = toNits(luminances.min * 10000), - .max_cll = toNits(settings.maxCLL), - .max_fall = toNits(settings.maxFALL), + .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, + .max_display_mastering_luminance = toNits(luminances.max), + .min_display_mastering_luminance = toNits(luminances.min * 10000), + .max_cll = toNits(settings.maxCLL), + .max_fall = toNits(settings.maxFALL), }, }; } @@ -1495,12 +1457,14 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (inFD.isValid()) pMonitor->output->state->setExplicitInFence(inFD.get()); - static auto PHDR = CConfigValue("experimental:hdr"); + static auto PPASS = CConfigValue("render:cm_fs_passthrough"); + const bool PHDR = pMonitor->imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ; const bool SUPPORTSPQ = pMonitor->output->parsedEDID.hdrMetadata.has_value() ? pMonitor->output->parsedEDID.hdrMetadata->supportsPQ : false; Debug::log(TRACE, "ColorManagement supportsBT2020 {}, supportsPQ {}", pMonitor->output->parsedEDID.supportsBT2020, SUPPORTSPQ); + if (pMonitor->output->parsedEDID.supportsBT2020 && SUPPORTSPQ) { - if (pMonitor->activeWorkspace && pMonitor->activeWorkspace->m_bHasFullscreenWindow && pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN) { + if (*PPASS && pMonitor->activeWorkspace && pMonitor->activeWorkspace->m_bHasFullscreenWindow && pMonitor->activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN) { const auto WINDOW = pMonitor->activeWorkspace->getFullscreenWindow(); const auto ROOT_SURF = WINDOW->m_pWLSurface->resource(); const auto SURF = @@ -1512,18 +1476,17 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { SURF->colorManagement->setHDRMetadata(createHDRMetadata(SURF->colorManagement->imageDescription(), pMonitor->output->parsedEDID)); if (needsHdrMetadataUpdate) pMonitor->output->state->setHDRMetadata(SURF->colorManagement->hdrMetadata()); - } else if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != *PHDR) - pMonitor->output->state->setHDRMetadata(*PHDR ? createHDRMetadata(2, pMonitor->output->parsedEDID) : createHDRMetadata(0, pMonitor->output->parsedEDID)); + } else if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != PHDR) + pMonitor->output->state->setHDRMetadata(PHDR ? createHDRMetadata(pMonitor->imageDescription, pMonitor->output->parsedEDID) : NO_HDR_METADATA); pMonitor->m_previousFSWindow = WINDOW; } else { - if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != *PHDR) - pMonitor->output->state->setHDRMetadata(*PHDR ? createHDRMetadata(2, pMonitor->output->parsedEDID) : createHDRMetadata(0, pMonitor->output->parsedEDID)); + if ((pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2) != PHDR) + pMonitor->output->state->setHDRMetadata(PHDR ? createHDRMetadata(pMonitor->imageDescription, pMonitor->output->parsedEDID) : NO_HDR_METADATA); pMonitor->m_previousFSWindow.reset(); } } - static auto PWIDE = CConfigValue("experimental:wide_color_gamut"); - const bool needsWCG = *PWIDE || pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2; + const bool needsWCG = pMonitor->output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2 || pMonitor->imageDescription.primariesNamed == CM_PRIMARIES_BT2020; if (pMonitor->output->state->state().wideColorGamut != needsWCG) { Debug::log(TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off"); pMonitor->output->state->setWideColorGamut(needsWCG); diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 666ee9d45..bcc252684 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -11,6 +11,17 @@ class CShader { GLint proj = -1; GLint color = -1; GLint alphaMatte = -1; + GLint texType = -1; + GLint skipCM = -1; + GLint sourceTF = -1; + GLint targetTF = -1; + GLint sourcePrimaries = -1; + GLint targetPrimaries = -1; + GLint maxLuminance = -1; + GLint dstMaxLuminance = -1; + GLint dstRefLuminance = -1; + GLint sdrSaturation = -1; // sdr -> hdr saturation + GLint sdrBrightness = -1; // sdr -> hdr brightness multiplier GLint tex = -1; GLint alpha = -1; GLint posAttrib = -1; diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index a5d3d7c00..46c6d2214 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -18,6 +18,7 @@ CSurfacePassElement::CSurfacePassElement(const CSurfacePassElement::SRenderData& void CSurfacePassElement::draw(const CRegion& damage) { g_pHyprOpenGL->m_RenderData.currentWindow = data.pWindow; + g_pHyprOpenGL->m_RenderData.surface = data.surface; g_pHyprOpenGL->m_RenderData.currentLS = data.pLS; g_pHyprOpenGL->m_RenderData.clipBox = data.clipBox; g_pHyprOpenGL->m_RenderData.discardMode = data.discardMode; @@ -36,6 +37,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; g_pHyprOpenGL->m_bEndFrame = false; g_pHyprOpenGL->m_RenderData.currentWindow.reset(); + g_pHyprOpenGL->m_RenderData.surface.reset(); g_pHyprOpenGL->m_RenderData.currentLS.reset(); }}; diff --git a/src/render/shaders/CM.frag b/src/render/shaders/CM.frag new file mode 100644 index 000000000..dd695d440 --- /dev/null +++ b/src/render/shaders/CM.frag @@ -0,0 +1,439 @@ +R"#( +#version 320 es +//#extension GL_OES_EGL_image_external : require + +precision highp float; +in vec2 v_texcoord; +uniform sampler2D tex; +//uniform samplerExternalOES texture0; + +uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext +uniform int skipCM; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat4x2 sourcePrimaries; +uniform mat4x2 targetPrimaries; +uniform float maxLuminance; +uniform float dstMaxLuminance; +uniform float dstRefLuminance; +uniform float sdrSaturation; +uniform float sdrBrightnessMultiplier; + +uniform float alpha; + +uniform vec2 topLeft; +uniform vec2 fullSize; +uniform float radius; +uniform float roundingPower; + +uniform int discardOpaque; +uniform int discardAlpha; +uniform float discardAlphaValue; + +uniform int applyTint; +uniform vec3 tint; + +//enum eTransferFunction +#define CM_TRANSFER_FUNCTION_BT1886 1 +#define CM_TRANSFER_FUNCTION_GAMMA22 2 +#define CM_TRANSFER_FUNCTION_GAMMA28 3 +#define CM_TRANSFER_FUNCTION_ST240 4 +#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 +#define CM_TRANSFER_FUNCTION_LOG_100 6 +#define CM_TRANSFER_FUNCTION_LOG_316 7 +#define CM_TRANSFER_FUNCTION_XVYCC 8 +#define CM_TRANSFER_FUNCTION_SRGB 9 +#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 +#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 +#define CM_TRANSFER_FUNCTION_ST428 12 +#define CM_TRANSFER_FUNCTION_HLG 13 + +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_INV_POW (1.0 / SRGB_POW) +#define SRGB_D_CUT 0.04045 +#define SRGB_E_CUT 0.0031308 +#define SRGB_LO 12.92 +#define SRGB_HI 1.055 +#define SRGB_HI_ADD 0.055 + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT (sqrt(3.0) * pow(HLG_D_CUT, 0.5)) +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +#define SDR_MIN_LUMINANCE 0.2 +#define SDR_MAX_LUMINANCE 80.0 +#define HDR_MIN_LUMINANCE 0.005 +#define HDR_MAX_LUMINANCE 10000.0 +#define HLG_MAX_LUMINANCE 1000.0 + +// smoothing constant for the edge: more = blurrier, but smoother +#define M_PI 3.1415926535897932384626433832795 +#define M_E 2.718281828459045 +#define SMOOTHING_CONSTANT (M_PI / 5.34665792551) + +vec4 rounding(vec4 color) { + highp vec2 pixCoord = vec2(gl_FragCoord); + pixCoord -= topLeft + fullSize * 0.5; + pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoord -= fullSize * 0.5 - radius; + pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left + + if (pixCoord.x + pixCoord.y > radius) { + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); + + if (dist > radius + SMOOTHING_CONSTANT) + discard; + + float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + + color *= normalized; + } + + return color; +} + +vec3 xy2xyz(vec2 xy) { + if (xy.y == 0.0) + return vec3(0.0, 0.0, 0.0); + + return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); +} + +vec4 saturate(vec4 color, mat3 primaries, float saturation) { + if (saturation == 1.0) + return color; + vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); + float Y = dot(color.rgb, brightness); + return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); +} + +vec3 toLinearRGB(vec3 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + bvec3 isLow; + vec3 lo; + vec3 hi; + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); + return pow( + (max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), + vec3(PQ_INV_M1) + ); + case CM_TRANSFER_FUNCTION_GAMMA22: + return pow(max(color.rgb, vec3(0.0)), vec3(2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: + return pow(max(color.rgb, vec3(0.0)), vec3(2.8)); + case CM_TRANSFER_FUNCTION_HLG: + isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); + lo = sqrt(3.0) * pow(color.rgb, vec3(0.5)); + hi = HLG_A * log(12.0 * color.rgb - HLG_B) + HLG_C; + return mix(hi, lo, isLow); + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + + case CM_TRANSFER_FUNCTION_SRGB: + default: + isLow = lessThanEqual(color.rgb, vec3(SRGB_D_CUT)); + lo = color.rgb / SRGB_LO; + hi = pow((color.rgb + SRGB_HI_ADD) / SRGB_HI, vec3(SRGB_POW)); + return mix(hi, lo, isLow); + } +} + +vec4 toLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = toLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 toNit(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb * SDR_MAX_LUMINANCE; + else { + + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + color.rgb = color.rgb * (HDR_MAX_LUMINANCE - HDR_MIN_LUMINANCE) + HDR_MIN_LUMINANCE; break; + case CM_TRANSFER_FUNCTION_HLG: + color.rgb = color.rgb * (HLG_MAX_LUMINANCE - HDR_MIN_LUMINANCE) + HDR_MIN_LUMINANCE; break; + case CM_TRANSFER_FUNCTION_GAMMA22: + case CM_TRANSFER_FUNCTION_GAMMA28: + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: + color.rgb = color.rgb * (SDR_MAX_LUMINANCE - SDR_MIN_LUMINANCE) + SDR_MIN_LUMINANCE; break; + } + } + return color; +} + +vec3 fromLinearRGB(vec3 color, int tf) { + bvec3 isLow; + vec3 lo; + vec3 hi; + + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: + return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); + return pow( + (vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), + vec3(PQ_M2) + ); + break; + case CM_TRANSFER_FUNCTION_GAMMA22: + return pow(max(color.rgb, vec3(0.0)), vec3(1.0 / 2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: + return pow(max(color.rgb, vec3(0.0)), vec3(1.0 / 2.8)); + case CM_TRANSFER_FUNCTION_HLG: + isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); + lo = pow(color.rgb / sqrt(3.0), vec3(2.0)); + hi = (pow(vec3(M_E), (color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; + return mix(hi, lo, isLow); + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + + case CM_TRANSFER_FUNCTION_SRGB: + default: + isLow = lessThanEqual(color.rgb, vec3(SRGB_E_CUT)); + lo = color.rgb * SRGB_LO; + hi = pow(color.rgb, vec3(SRGB_INV_POW)) * SRGB_HI - SRGB_HI_ADD; + return mix(hi, lo, isLow); + } +} + +vec4 fromLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 fromLinearNit(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb / SDR_MAX_LUMINANCE; + else { + color.rgb /= max(color.a, 0.001); + + switch (tf) { + case CM_TRANSFER_FUNCTION_ST2084_PQ: + color.rgb = (color.rgb - HDR_MIN_LUMINANCE) / (HDR_MAX_LUMINANCE - HDR_MIN_LUMINANCE); break; + case CM_TRANSFER_FUNCTION_HLG: + color.rgb = (color.rgb - HDR_MIN_LUMINANCE) / (HLG_MAX_LUMINANCE - HDR_MIN_LUMINANCE); break; + case CM_TRANSFER_FUNCTION_GAMMA22: + case CM_TRANSFER_FUNCTION_GAMMA28: + case CM_TRANSFER_FUNCTION_BT1886: + case CM_TRANSFER_FUNCTION_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: + color.rgb = (color.rgb - SDR_MIN_LUMINANCE) / (SDR_MAX_LUMINANCE - SDR_MIN_LUMINANCE); break; + } + + color.rgb = fromLinearRGB(color.rgb, tf); + + color.rgb *= color.a; + } + return color; +} + +mat3 primaries2xyz(mat4x2 primaries) { + vec3 r = xy2xyz(primaries[0]); + vec3 g = xy2xyz(primaries[1]); + vec3 b = xy2xyz(primaries[2]); + vec3 w = xy2xyz(primaries[3]); + + mat3 invMat = inverse( + mat3( + r.x, r.y, r.z, + g.x, g.y, g.z, + b.x, b.y, b.z + ) + ); + + vec3 s = invMat * w; + + return mat3( + s.r * r.x, s.r * r.y, s.r * r.z, + s.g * g.x, s.g * g.y, s.g * g.z, + s.b * b.x, s.b * b.y, s.b * b.z + ); +} + +// const vec2 D65 = vec2(0.3127, 0.3290); +const mat3 Bradford = mat3( + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296 +); +const mat3 BradfordInv = inverse(Bradford); + +mat3 adaptWhite(vec2 src, vec2 dst) { + if (src == dst) + return mat3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ); + + vec3 srcXYZ = xy2xyz(src); + vec3 dstXYZ = xy2xyz(dst); + vec3 factors = (Bradford * dstXYZ) / (Bradford * srcXYZ); + + return BradfordInv * mat3( + factors.x, 0.0, 0.0, + 0.0, factors.y, 0.0, + 0.0, 0.0, factors.z + ) * Bradford; +} + +vec4 convertPrimaries(vec4 color, mat3 src, vec2 srcWhite, mat3 dst, vec2 dstWhite) { + mat3 convMat = inverse(dst) * adaptWhite(srcWhite, dstWhite) * src; + return vec4(convMat * color.rgb, color[3]); +} + +const mat3 BT2020toLMS = mat3( + 0.3592, 0.6976, -0.0358, + -0.1922, 1.1004, 0.0755, + 0.0070, 0.0749, 0.8434 +); +const mat3 LMStoBT2020 = inverse(BT2020toLMS); + +const mat3 ICtCpPQ = transpose(mat3( + 2048.0, 2048.0, 0.0, + 6610.0, -13613.0, 7003.0, + 17933.0, -17390.0, -543.0 +) / 4096.0); +const mat3 ICtCpPQInv = inverse(ICtCpPQ); + +const mat3 ICtCpHLG = transpose(mat3( + 2048.0, 2048.0, 0.0, + 3625.0, -7465.0, 3840.0, + 9500.0, -9212.0, -288.0 +) / 4096.0); +const mat3 ICtCpHLGInv = inverse(ICtCpHLG); + +vec4 tonemap(vec4 color, mat3 dstXYZ) { + if (maxLuminance < dstMaxLuminance * 1.01) + return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); + + mat3 toLMS = BT2020toLMS * dstXYZ; + mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; + + vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; + vec3 ICtCp = ICtCpPQ * lms; + + float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); + float luminance = pow( + (max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), + PQ_INV_M1 + ) * HDR_MAX_LUMINANCE; + + float srcScale = maxLuminance / dstRefLuminance; + float dstScale = dstMaxLuminance / dstRefLuminance; + + float minScale = min(srcScale, 1.5); + float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale); + float refLuminance = dstRefLuminance * dimming; + + float low = min(luminance * dimming, refLuminance); + float highlight = clamp((luminance / dstRefLuminance - 1.0) / (srcScale - 1.0), 0.0, 1.0); + float high = log(highlight * (M_E - 1.0) + 1.0) * (dstMaxLuminance - refLuminance); + luminance = low + high; + + E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_M1); + ICtCp[0] = pow( + (PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), + PQ_M2 + ) / HDR_MAX_LUMINANCE; + return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]); +} + +layout(location = 0) out vec4 fragColor; +void main() { + vec4 pixColor; + if (texType == 1) + pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); +// else if (texType == 2) +// pixColor = texture(texture0, v_texcoord); + else // assume rgba + pixColor = texture(tex, v_texcoord); + + if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) + discard; + + if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) + discard; + + if (skipCM == 0) { + pixColor.rgb /= max(pixColor.a, 0.001); + pixColor.rgb = toLinearRGB(pixColor.rgb, sourceTF); + mat3 srcxyz = primaries2xyz(sourcePrimaries); + mat3 dstxyz; + if (sourcePrimaries == targetPrimaries) + dstxyz = srcxyz; + else { + dstxyz = primaries2xyz(targetPrimaries); + pixColor = convertPrimaries(pixColor, srcxyz, sourcePrimaries[3], dstxyz, targetPrimaries[3]); + } + pixColor = toNit(pixColor, sourceTF); + pixColor.rgb *= pixColor.a; + pixColor = tonemap(pixColor, dstxyz); + if (sourceTF == CM_TRANSFER_FUNCTION_SRGB && targetTF == CM_TRANSFER_FUNCTION_ST2084_PQ) + pixColor = saturate(pixColor, srcxyz, sdrSaturation); + pixColor *= sdrBrightnessMultiplier; + pixColor = fromLinearNit(pixColor, targetTF); + } + + if (applyTint == 1) + pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); + + if (radius > 0.0) + pixColor = rounding(pixColor); + + fragColor = pixColor * alpha; +} +)#" diff --git a/src/render/shaders/Textures.hpp b/src/render/shaders/Textures.hpp index 788453223..002c34598 100644 --- a/src/render/shaders/Textures.hpp +++ b/src/render/shaders/Textures.hpp @@ -94,6 +94,10 @@ void main() { v_texcoord = texcoord; })#"; +inline const std::string TEXFRAGSRCCM = +#include "CM.frag" + ; + inline const std::string TEXFRAGSRCRGBA = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1