diff --git a/CMakeLists.txt b/CMakeLists.txt index 87461645..c5e2de46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,7 +266,8 @@ pkg_check_modules( gbm gio-2.0 re2 - muparser) + muparser + lcms2) find_package(hyprwayland-scanner 0.3.10 REQUIRED) diff --git a/nix/default.nix b/nix/default.nix index af1503f9..6a6b4abc 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -21,6 +21,7 @@ hyprutils, hyprwayland-scanner, hyprwire, + lcms2, libGL, libdrm, libexecinfo, @@ -179,6 +180,7 @@ customStdenv.mkDerivation (finalAttrs: { hyprlang hyprutils hyprwire + lcms2 libdrm libgbm libGL diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 11aec350..6d2044fe 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -9,7 +9,7 @@ #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" #include "desktop/view/Window.hpp" -#include "protocols/types/ColorManagement.hpp" +#include "helpers/cm/ColorManagement.hpp" #include #include diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index cb38154e..e62130a4 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -797,6 +797,7 @@ CConfigManager::CConfigManager() { registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); registerConfigVar("render:cm_sdr_eotf", {"default"}); registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1}); + registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); @@ -871,6 +872,7 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); + m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""}); // windowrule v3 m_config->addSpecialCategory("windowrule", {.key = "name"}); @@ -1257,6 +1259,10 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.rule().maxAvgLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().iccFile = std::any_cast(VAL->getValue()); + auto newrule = parser.rule(); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); @@ -2275,6 +2281,15 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) { return true; } +bool CMonitorRuleParser::parseICC(const std::string& val) { + if (val.empty()) { + m_error += "invalid icc "; + return false; + } + m_rule.iccFile = val; + return true; +} + void CMonitorRuleParser::setDisabled() { m_rule.disabled = true; } @@ -2369,6 +2384,9 @@ std::optional CConfigManager::handleMonitor(const std::string& comm } else if (ARGS[argno] == "vrr") { parser.parseVRR(std::string(ARGS[argno + 1])); argno++; + } else if (ARGS[argno] == "icc") { + parser.parseICC(std::string(ARGS[argno + 1])); + argno++; } else if (ARGS[argno] == "workspace") { const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 21a3c58c..36e25f6b 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -177,6 +177,7 @@ class CMonitorRuleParser { bool parseSDRBrightness(const std::string& value); bool parseSDRSaturation(const std::string& value); bool parseVRR(const std::string& value); + bool parseICC(const std::string& value); void setDisabled(); void setMirror(const std::string& value); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 3be5be40..626c3bce 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -30,7 +30,7 @@ #include "../hyprerror/HyprError.hpp" #include "../layout/LayoutManager.hpp" #include "../i18n/Engine.hpp" -#include "../protocols/types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" #include "../desktop/view/LayerSurface.hpp" @@ -933,29 +933,46 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_supportsWideColor = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR; - m_cmType = RULE->cmType; - switch (m_cmType) { - case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; - case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; - case NCMType::CM_HDR: - case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; - default: break; + if (RULE->iccFile.empty()) { + // only apply explicit cm settings if we have no icc file + + m_cmType = RULE->cmType; + switch (m_cmType) { + case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; + case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; + case NCMType::CM_HDR: + case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; + default: break; + } + + m_sdrEotf = RULE->sdrEotf; + + m_sdrMinLuminance = RULE->sdrMinLuminance; + m_sdrMaxLuminance = RULE->sdrMaxLuminance; + + m_minLuminance = RULE->minLuminance; + m_maxLuminance = RULE->maxLuminance; + m_maxAvgLuminance = RULE->maxAvgLuminance; + + applyCMType(m_cmType, m_sdrEotf); + + m_sdrSaturation = RULE->sdrSaturation; + m_sdrBrightness = RULE->sdrBrightness; + } else { + auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile); + if (!image) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + } else { + m_imageDescription = CImageDescription::from(*image); + if (!m_imageDescription) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + m_imageDescription = CImageDescription::from(SImageDescription{}); + } + } } - m_sdrEotf = RULE->sdrEotf; - - m_sdrMinLuminance = RULE->sdrMinLuminance; - m_sdrMaxLuminance = RULE->sdrMaxLuminance; - - m_minLuminance = RULE->minLuminance; - m_maxLuminance = RULE->maxLuminance; - m_maxAvgLuminance = RULE->maxAvgLuminance; - - applyCMType(m_cmType, m_sdrEotf); - - m_sdrSaturation = RULE->sdrSaturation; - m_sdrBrightness = RULE->sdrBrightness; - Vector2D logicalSize = m_pixelSize / m_scale; if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { // invalid scale, will produce fractional pixels. @@ -1037,6 +1054,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_damage.setSize(m_transformedSize); + updateVCGTRamps(); + // Set scale for all surfaces on this monitor, needed for some clients // but not on unsafe state to avoid crashes if (!g_pCompositor->m_unsafeState) { @@ -2187,8 +2206,8 @@ bool CMonitor::canNoShaderCM() { const auto SRC_DESC_VALUE = SRC_DESC.value()->value(); - if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0) - return false; // no ICC support + if (m_imageDescription->value().icc.present) + return false; const auto sdrEOTF = NTransferFunction::fromConfig(); // only primaries differ @@ -2207,6 +2226,71 @@ bool CMonitor::doesNoShaderCM() { return m_noShaderCTM; } +static std::vector resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) { + std::vector out; + out.resize(gammaSize * 3); + + // + auto sample = [&](int c, float x) -> uint16_t { + const float maxX = t.entries - 1; + x = std::clamp(x, 0.F, maxX); + + const size_t i0 = (size_t)std::floor(x); + const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1); + const float f = x - sc(i0); + + const float v0 = sc(t.ch[c][i0]); + const float v1 = sc(t.ch[c][i1]); + const float v = v0 + ((v1 - v0) * f); + + int64_t vi = std::round(v); + vi = std::clamp(vi, sc(0), sc(65535)); + return sc(vi); + }; + + for (size_t i = 0; i < gammaSize; ++i) { + float x = sc(i) * sc(t.entries - 1) / sc(gammaSize - 1); + + const uint16_t r = sample(0, x); + const uint16_t g = sample(1, x); + const uint16_t b = sample(2, x); + + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + + return out; +} + +void CMonitor::updateVCGTRamps() { + auto gammaSize = m_output->getGammaSize(); + + if (gammaSize <= 10) { + Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output"); + return; + } + + if (!m_imageDescription->value().icc.vcgt) { + if (m_vcgtRampsSet) + m_output->state->setGammaLut({}); + + m_vcgtRampsSet = false; + return; + } + + // build table + auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize); + + m_output->state->setGammaLut(table); + + m_vcgtRampsSet = true; +} + +bool CMonitor::gammaRampsInUse() { + return m_vcgtRampsSet; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 72e0cf66..dd14a19b 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -16,7 +16,7 @@ #include "math/Math.hpp" #include "../desktop/reserved/ReservedArea.hpp" #include -#include "../protocols/types/ColorManagement.hpp" +#include "cm/ColorManagement.hpp" #include "signal/Signal.hpp" #include "DamageRing.hpp" #include @@ -56,6 +56,7 @@ struct SMonitorRule { float sdrSaturation = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR Desktop::CReservedArea reservedArea; + std::string iccFile; int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable @@ -332,6 +333,7 @@ class CMonitor { bool wantsHDR(); bool inHDR(); + bool gammaRampsInUse(); /// Has an active workspace with a real fullscreen window (includes special workspace) bool inFullscreenMode(); @@ -353,8 +355,8 @@ class CMonitor { PHLWINDOWREF m_previousFSWindow; bool m_needsHDRupdate = false; - NColorManagement::PImageDescription m_imageDescription; - bool m_noShaderCTM = false; // sets drm CTM, restore needed + NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); + bool m_noShaderCTM = false; // sets drm CTM, restore needed // For the list lookup @@ -366,8 +368,10 @@ class CMonitor { void setupDefaultWS(const SMonitorRule&); WORKSPACEID findAvailableDefaultWS(); void commitDPMSState(bool state); + void updateVCGTRamps(); bool m_doneScheduled = false; + bool m_vcgtRampsSet = false; std::stack m_prevWorkSpaces; struct { diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp new file mode 100644 index 00000000..bac9f25a --- /dev/null +++ b/src/helpers/cm/ColorManagement.cpp @@ -0,0 +1,193 @@ +#include "ColorManagement.hpp" +#include "../../macros.hpp" +#include +#include +#include + +using namespace NColorManagement; + +namespace NColorManagement { + // expected to be small + static std::vector> knownPrimaries; + static std::vector> knownDescriptions; + static std::map, Hyprgraphics::CMatrix3> primariesConversion; +} + +const SPCPRimaries& NColorManagement::getPrimaries(ePrimaries name) { + switch (name) { + case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709; + case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020; + case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M; + case CM_PRIMARIES_PAL: return NColorPrimaries::PAL; + case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC; + case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM; + case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ; + case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3; + case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3; + case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB; + default: return NColorPrimaries::DEFAULT_PRIMARIES; + } +} + +CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint32_t primariesId) : m_id(primariesId), m_primaries(primaries) { + m_primaries2XYZ = m_primaries.toXYZ(); +} + +WP CPrimaries::from(const SPCPRimaries& primaries) { + for (const auto& known : knownPrimaries) { + if (known->value() == primaries) + return known; + } + + knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1))); + return knownPrimaries.back(); +} + +WP CPrimaries::from(const ePrimaries name) { + return from(getPrimaries(name)); +} + +WP CPrimaries::from(const uint32_t primariesId) { + ASSERT(primariesId <= knownPrimaries.size()); + return knownPrimaries[primariesId - 1]; +} + +const SPCPRimaries& CPrimaries::value() const { + return m_primaries; +} + +uint CPrimaries::id() const { + return m_id; +} + +const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const { + return m_primaries2XYZ; +} + +const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP dst) const { + const auto cacheKey = std::make_pair(m_id, dst->m_id); + if (!primariesConversion.contains(cacheKey)) + primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries))); + + return primariesConversion[cacheKey]; +} + +CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint32_t imageDescriptionId) : + m_id(imageDescriptionId), m_imageDescription(imageDescription) { + m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id(); +} + +PImageDescription CImageDescription::from(const SImageDescription& imageDescription) { + for (const auto& known : knownDescriptions) { + if (known->value() == imageDescription) + return known; + } + + knownDescriptions.emplace_back(UP(new CImageDescription(imageDescription, knownDescriptions.size() + 1))); + return knownDescriptions.back(); +} + +PImageDescription CImageDescription::from(const uint32_t imageDescriptionId) { + ASSERT(imageDescriptionId <= knownDescriptions.size()); + return knownDescriptions[imageDescriptionId - 1]; +} + +PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const { + auto desc = m_imageDescription; + desc.luminances = luminances; + return CImageDescription::from(desc); +} + +const SImageDescription& CImageDescription::value() const { + return m_imageDescription; +} + +uint CImageDescription::id() const { + return m_id; +} + +WP CImageDescription::getPrimaries() const { + return CPrimaries::from(m_primariesId); +} + +static Mat3x3 diag3(const std::array& s) { + return Mat3x3{std::array{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}}; +} + +static std::optional invertMat3(const Mat3x3& m) { + const auto ARR = m.getMatrix(); + const double a = ARR[0], b = ARR[1], c = ARR[2]; + const double d = ARR[3], e = ARR[4], f = ARR[5]; + const double g = ARR[6], h = ARR[7], i = ARR[8]; + + const double A = (e * i - f * h); + const double B = -(d * i - f * g); + const double C = (d * h - e * g); + const double D = -(b * i - c * h); + const double E = (a * i - c * g); + const double F = -(a * h - b * g); + const double G = (b * f - c * e); + const double H = -(a * f - c * d); + const double I = (a * e - b * d); + + const double det = a * A + b * B + c * C; + if (std::abs(det) < 1e-18) + return std::nullopt; + + const double invDet = 1.0 / det; + Mat3x3 inv{std::array{ + A * invDet, + D * invDet, + G * invDet, // + B * invDet, + E * invDet, + H * invDet, // + C * invDet, + F * invDet, + I * invDet, // + }}; + return inv; +} + +static std::array matByVec(const Mat3x3& M, const std::array& v) { + const auto ARR = M.getMatrix(); + return {ARR[0] * v[0] + ARR[1] * v[1] + ARR[2] * v[2], ARR[3] * v[0] + ARR[4] * v[1] + ARR[5] * v[2], ARR[6] * v[0] + ARR[7] * v[1] + ARR[8] * v[2]}; +} + +std::optional NColorManagement::rgbToXYZFromPrimaries(SPCPRimaries pr) { + const auto R = Hyprgraphics::xy2xyz(pr.red); + const auto G = Hyprgraphics::xy2xyz(pr.green); + const auto B = Hyprgraphics::xy2xyz(pr.blue); + const auto W = Hyprgraphics::xy2xyz(pr.white); + + // P has columns R,G,B + Mat3x3 P{std::array{R.x, G.x, B.x, R.y, G.y, B.y, R.z, G.z, B.z}}; + + auto invP = invertMat3(P); + if (!invP) + return std::nullopt; + + const auto S = matByVec(*invP, {W.x, W.y, W.z}); + + P.multiply(diag3(S)); // RGB->XYZ + + return P; +} + +Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW) { + static const Mat3x3 Bradford{std::array{0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f}}; + static const Mat3x3 BradfordInv = invertMat3(Bradford).value(); + + const auto srcXYZ = Hyprgraphics::xy2xyz(srcW); + const auto dstXYZ = Hyprgraphics::xy2xyz(dstW); + + const auto srcLMS = matByVec(Bradford, {srcXYZ.x, srcXYZ.y, srcXYZ.z}); + const auto dstLMS = matByVec(Bradford, {dstXYZ.x, dstXYZ.y, dstXYZ.z}); + + const std::array scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]}; + + Mat3x3 result = BradfordInv; + result.multiply(diag3(scale)).multiply(Bradford); + + return result; +} \ No newline at end of file diff --git a/src/protocols/types/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp similarity index 87% rename from src/protocols/types/ColorManagement.hpp rename to src/helpers/cm/ColorManagement.hpp index c1f58316..e8d47fae 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -3,6 +3,11 @@ #include "color-management-v1.hpp" #include #include "../../helpers/memory/Memory.hpp" +#include "../../helpers/math/Math.hpp" + +#include +#include +#include #define SDR_MIN_LUMINANCE 0.2 #define SDR_MAX_LUMINANCE 80.0 @@ -12,6 +17,8 @@ #define HDR_REF_LUMINANCE 203.0 #define HLG_MAX_LUMINANCE 1000.0 +class CTexture; + namespace NColorManagement { enum eNoShader : uint8_t { CM_NS_DISABLE = 0, @@ -67,7 +74,6 @@ namespace NColorManagement { using SPCPRimaries = Hyprgraphics::SPCPRimaries; namespace NColorPrimaries { - static const auto DEFAULT_PRIMARIES = SPCPRimaries{}; static const auto BT709 = SPCPRimaries{ .red = {.x = 0.64, .y = 0.33}, @@ -76,6 +82,8 @@ namespace NColorManagement { .white = {.x = 0.3127, .y = 0.3290}, }; + static const auto DEFAULT_PRIMARIES = BT709; + static const auto PAL_M = SPCPRimaries{ .red = {.x = 0.67, .y = 0.33}, .green = {.x = 0.21, .y = 0.71}, @@ -140,7 +148,16 @@ namespace NColorManagement { }; } - const SPCPRimaries& getPrimaries(ePrimaries name); + struct SVCGTTable16 { + uint16_t channels = 0; + uint16_t entries = 0; + uint16_t entrySize = 0; + std::array, 3> ch; + }; + + const SPCPRimaries& getPrimaries(ePrimaries name); + std::optional rgbToXYZFromPrimaries(SPCPRimaries pr); + Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW); class CPrimaries { public: @@ -163,22 +180,17 @@ namespace NColorManagement { }; struct SImageDescription { - struct SIccFile { - int fd = -1; - uint32_t length = 0; - uint32_t offset = 0; - bool operator==(const SIccFile& i2) const { - return fd == i2.fd; - } - } icc; + static std::expected fromICC(const std::filesystem::path& file); - bool windowsScRGB = false; + // + std::vector rawICC; - eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; - float transferFunctionPower = 1.0f; + eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; + float transferFunctionPower = 1.0f; + bool windowsScRGB = false; - bool primariesNameSet = false; - ePrimaries primariesNamed = CM_PRIMARIES_SRGB; + 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 1000000 // drm expects uint16_t values multiplied by 50000 @@ -202,11 +214,23 @@ namespace NColorManagement { } } masteringLuminances; + // Matrix data from ICC + struct SICCData { + bool present = false; + size_t lutSize = 33; + std::vector lutDataPacked; + SP lutTexture; + std::optional vcgt; + } icc; + uint32_t maxCLL = 0; uint32_t maxFALL = 0; bool operator==(const SImageDescription& d2) const { - return icc == d2.icc && windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && + if (icc.present || d2.icc.present) + return false; + + return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && (primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) && masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL && maxFALL == d2.maxFALL; @@ -280,19 +304,19 @@ namespace NColorManagement { class CImageDescription { public: static WP from(const SImageDescription& imageDescription); - static WP from(const uint imageDescriptionId); + static WP from(const uint32_t imageDescriptionId); WP with(const SImageDescription::SPCLuminances& luminances) const; const SImageDescription& value() const; - uint id() const; + uint32_t id() const; WP getPrimaries() const; private: CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId); - uint m_id; - uint m_primariesId; + uint32_t m_id = 0; + uint32_t m_primariesId = 0; SImageDescription m_imageDescription; }; @@ -311,8 +335,8 @@ namespace NColorManagement { .luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}}); ; static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ - .windowsScRGB = true, .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, + .windowsScRGB = true, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, .primaries = NColorPrimaries::BT709, diff --git a/src/helpers/cm/ICC.cpp b/src/helpers/cm/ICC.cpp new file mode 100644 index 00000000..34045543 --- /dev/null +++ b/src/helpers/cm/ICC.cpp @@ -0,0 +1,278 @@ +#include "ColorManagement.hpp" +#include "../math/Math.hpp" +#include +#include + +#include "../../debug/log/Logger.hpp" +#include "../../render/Texture.hpp" +#include "../../render/Renderer.hpp" + +#include +using namespace Hyprutils::Utils; + +#include + +using namespace NColorManagement; + +static std::vector readBinary(const std::filesystem::path& file) { + std::ifstream ifs(file, std::ios::binary); + if (!ifs.good()) + return {}; + + ifs.seekg(0, std::ios::end); + size_t len = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + + if (len <= 0) + return {}; + + std::vector buf; + buf.resize(len); + ifs.read(reinterpret_cast(buf.data()), len); + + return buf; +} + +static uint16_t bigEndianU16(const uint8_t* p) { + return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]); +} + +static uint32_t bigEndianU32(const uint8_t* p) { + return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; +} + +static constexpr cmsTagSignature makeSig(char a, char b, char c, char d) { + return sc(sc(a) << 24 | sc(b) << 16 | sc(c) << 8 | sc(d)); +} + +static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't'); + +// + +static std::expected, std::string> readVCGT16(cmsHPROFILE prof) { + if (!cmsIsTag(prof, VCGT_SIG)) + return std::nullopt; + + cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0); + if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header + return std::unexpected("Malformed vcgt tag"); + + std::vector raw(n); + if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n) + return std::unexpected("Malformed vcgt tag"); + + // raw layout: + // 0 ... 3: 'vcgt' + // 4 ... 7: reserved + // 8 ... 11: gammaType (0 = table) + uint32_t gammaType = bigEndianU32(raw.data() + 8); + if (gammaType != 0) + return std::unexpected("VCGT formula type is not supported by Hyprland"); + + SVCGTTable16 table; + table.channels = bigEndianU16(raw.data() + 12); + table.entries = bigEndianU16(raw.data() + 14); + table.entrySize = bigEndianU16(raw.data() + 16); + // raw+18: reserved u16 + + Log::logger->log(Log::DEBUG, "readVCGT16: table has {} channels, {} entries, and entry size of {}", table.channels, table.entries, table.entrySize); + + if (table.channels != 3 || table.entrySize != 2 || table.entries == 0) + return std::unexpected("invalid vcgt table size"); + + size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize; + + // VCGT is a piece of shit and some absolute fucking mongoloid idiots + // decided it'd be great to have both 18 and 20 + // FUCK YOU + size_t tableOff = 20; + + auto readTable = [&] -> void { + for (int c = 0; c < 3; ++c) { + table.ch[c].resize(table.entries); + for (uint16_t i = 0; i < table.entries; ++i) { + const uint8_t* p = raw.data() + tableOff + static_cast((c * table.entries + i) * 2); + table.ch[c][i] = bigEndianU16(p); // 0 ... 65535 + } + } + }; + + if (raw.size() < tableOff + tableBytes) { + tableOff = 18; + + if (raw.size() < tableOff + tableBytes) { + Log::logger->log(Log::ERR, "readVCGT16: table is too short, tag is invalid"); + return std::unexpected("table is too short"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18"); + + readTable(); + } else { + readTable(); + + // if the table's last entry is suspiciously low, we more than likely read an 18 as a 20. + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::DEBUG, "readVCGT16: table is likely offset 18 not 20, re-reading"); + + tableOff = 18; + + readTable(); + } + } + + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::ERR, "readVCGT16: table is malformed, last value of a gamma ramp can't be {}", table.ch[0][table.entries - 1]); + return std::unexpected("invalid table values"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: red channel: [{}, {}, ... {}, {}]", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: green channel: [{}, {}, ... {}, {}]", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: blue channel: [{}, {}, ... {}, {}]", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]); + + return table; +} + +struct CmsProfileDeleter { + void operator()(cmsHPROFILE p) const { + if (p) + cmsCloseProfile(p); + } +}; +struct CmsTransformDeleter { + void operator()(cmsHTRANSFORM t) const { + if (t) + cmsDeleteTransform(t); + } +}; + +using UniqueProfile = std::unique_ptr, CmsProfileDeleter>; +using UniqueTransform = std::unique_ptr, CmsTransformDeleter>; + +static UniqueProfile createLinearSRGBProfile() { + cmsCIExyYTRIPLE prim{}; + // sRGB / Rec.709 primaries + prim.Red.x = 0.6400; + prim.Red.y = 0.3300; + prim.Red.Y = 1.0; + prim.Green.x = 0.3000; + prim.Green.y = 0.6000; + prim.Green.Y = 1.0; + prim.Blue.x = 0.1500; + prim.Blue.y = 0.0600; + prim.Blue.Y = 1.0; + + cmsCIExyY wp{}; + wp.x = 0.3127; + wp.y = 0.3290; + wp.Y = 1.0; // D65 + + cmsToneCurve* lin = cmsBuildGamma(nullptr, 1.0); + cmsToneCurve* curves[3] = {lin, lin, lin}; + + cmsHPROFILE p = cmsCreateRGBProfile(&wp, &prim, curves); + + cmsFreeToneCurve(lin); + return UniqueProfile{p}; +} + +static std::expected buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) { + UniqueProfile src = createLinearSRGBProfile(); + if (!src) + return std::unexpected("Failed to create linear sRGB profile"); + + // Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe. + const int intent = INTENT_RELATIVE_COLORIMETRIC; + const cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS + + // float->float transform (linear input, encoded output in dst device space) + UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)}; + if (!xform) + return std::unexpected("Failed to create ICC transform"); + + Log::logger->log(Log::DEBUG, "Building a {}³ 3D LUT", image.icc.lutSize); + + image.icc.present = true; + image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3); + + auto idx = [&image](int r, int g, int b) -> size_t { + // + return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3; + }; + + for (size_t bz = 0; bz < image.icc.lutSize; ++bz) { + for (size_t gy = 0; gy < image.icc.lutSize; ++gy) { + for (size_t rx = 0; rx < image.icc.lutSize; ++rx) { + float in[3] = { + rx / float(image.icc.lutSize - 1), + gy / float(image.icc.lutSize - 1), + bz / float(image.icc.lutSize - 1), + }; + float outRGB[3]; + cmsDoTransform(xform.get(), in, outRGB, 1); + + outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F); + outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F); + outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F); + + const size_t o = idx(rx, gy, bz); + image.icc.lutDataPacked[o + 0] = outRGB[0]; + image.icc.lutDataPacked[o + 1] = outRGB[1]; + image.icc.lutDataPacked[o + 2] = outRGB[2]; + } + } + } + + Log::logger->log(Log::DEBUG, "3D LUT constructed, size {}", image.icc.lutDataPacked.size()); + + // upload + image.icc.lutTexture = makeShared(image.icc.lutDataPacked, image.icc.lutSize); + + return {}; +} + +std::expected SImageDescription::fromICC(const std::filesystem::path& file) { + static auto PVCGTENABLED = CConfigValue("render:icc_vcgt_enabled"); + + std::error_code ec; + if (!std::filesystem::exists(file, ec) || ec) + return std::unexpected("Invalid file"); + + SImageDescription image; + image.rawICC = readBinary(file); + + if (image.rawICC.empty()) + return std::unexpected("Failed to read file"); + + cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), "r"); + if (!prof) + return std::unexpected("CMS failed to open icc file"); + + CScopeGuard x([&prof] { cmsCloseProfile(prof); }); + + // only handle RGB (typical display profiles) + if (cmsGetColorSpace(prof) != cmsSigRgbData) + return std::unexpected("Only RGB display profiles are supported"); + + Log::logger->log(Log::DEBUG, "============= Begin ICC load ============="); + Log::logger->log(Log::DEBUG, "ICC size: {} bytes", image.rawICC.size()); + + if (const auto RET = buildIcc3DLut(prof, image); !RET) + return std::unexpected(RET.error()); + + if (*PVCGTENABLED) { + auto vcgtRes = readVCGT16(prof); + if (!vcgtRes) + return std::unexpected(vcgtRes.error()); + + image.icc.vcgt = *vcgtRes; + + if (!*vcgtRes) + Log::logger->log(Log::DEBUG, "ICC profile has no VCGT data"); + } else + Log::logger->log(Log::DEBUG, "Skipping VCGT load, disabled by config"); + + Log::logger->log(Log::DEBUG, "============= End ICC load ============="); + + return image; +} \ No newline at end of file diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index 9ebbbde3..11f54fec 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -597,7 +597,7 @@ SP CPointerManager::renderHWCursorBuffer(SPlog(Log::TRACE, "[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}", state->monitor->m_name, m_currentCursorImage.size, cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size()); - g_pHyprOpenGL->renderTexture(texture, xbox, {}); + g_pHyprOpenGL->renderTexture(texture, xbox, {.noCM = true}); g_pHyprOpenGL->end(); g_pHyprOpenGL->m_renderData.pMonitor.reset(); diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp index 73ccf958..3c3438f8 100644 --- a/src/managers/screenshare/ScreenshareFrame.cpp +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -159,9 +159,11 @@ void CScreenshareFrame::renderMonitor() { const auto PMONITOR = m_session->monitor(); - auto TEXTURE = makeShared(PMONITOR->m_output->state->state().buffer); + if (!g_pHyprOpenGL->m_monitorRenderResources.contains(PMONITOR)) + return; // wtf? + + auto TEXTURE = g_pHyprOpenGL->m_monitorRenderResources[PMONITOR].monitorMirrorFB.getTexture(); - const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client); g_pHyprOpenGL->m_renderData.transformDamage = false; g_pHyprOpenGL->m_renderData.noSimplify = true; @@ -171,11 +173,7 @@ void CScreenshareFrame::renderMonitor() { .translate(-m_session->m_captureBox.pos()); // vvvv kinda ass-backwards but that's how I designed the renderer... sigh. g_pHyprOpenGL->pushMonitorTransformEnabled(true); g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTexture(TEXTURE, monbox, - { - .cmBackToSRGB = !IS_CM_AWARE, - .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, - }); + g_pHyprOpenGL->renderTexturePrimitive(TEXTURE, monbox); g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); diff --git a/src/managers/screenshare/ScreenshareManager.cpp b/src/managers/screenshare/ScreenshareManager.cpp index 823e99b3..57b9f6ef 100644 --- a/src/managers/screenshare/ScreenshareManager.cpp +++ b/src/managers/screenshare/ScreenshareManager.cpp @@ -156,6 +156,14 @@ void CScreenshareManager::destroyClientSessions(wl_client* client) { std::erase_if(m_managedSessions, [&](const auto& session) { return !session || session->m_session->m_client == client; }); } +bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) { + return std::ranges::any_of(m_pendingFrames, [monitor](const auto& f) { + if (!f || !f->m_session) + return false; + return (f->m_session->m_type == SHARE_MONITOR || f->m_session->m_type == SHARE_REGION) && f->m_session->m_monitor == monitor; + }); +} + CScreenshareManager::SManagedSession::SManagedSession(UP&& session) : m_session(std::move(session)) { ; } diff --git a/src/managers/screenshare/ScreenshareManager.hpp b/src/managers/screenshare/ScreenshareManager.hpp index 4c61a7b0..afd35426 100644 --- a/src/managers/screenshare/ScreenshareManager.hpp +++ b/src/managers/screenshare/ScreenshareManager.hpp @@ -209,6 +209,7 @@ namespace Screenshare { void destroyClientSessions(wl_client* client); void onOutputCommit(PHLMONITOR monitor); + bool isOutputBeingSSd(PHLMONITOR monitor); private: std::vector> m_sessions; diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index 90840217..b9c3143b 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -3,7 +3,7 @@ #include "color-management-v1.hpp" #include "../helpers/Monitor.hpp" #include "core/Output.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include using namespace NColorManagement; @@ -388,12 +388,6 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_settings = m_surface->getPreferredImageDescription(); m_currentPreferredId = RESOURCE->m_settings->id(); - if (!PROTO::colorManagement->m_debug && RESOURCE->m_settings->value().icc.fd >= 0) { - LOGM(Log::ERR, "FIXME: parse icc profile"); - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); - return; - } - RESOURCE->resource()->sendReady(m_currentPreferredId); }); @@ -429,7 +423,7 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings"); return; } @@ -443,10 +437,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPresource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported"); return; } @@ -459,9 +453,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPsetSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) { - m_settings.icc.fd = fd; - m_settings.icc.offset = offset; - m_settings.icc.length = length; + m_icc.fd = fd; + m_icc.offset = offset; + m_icc.length = length; }); } @@ -731,8 +725,9 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP(std::round(value * PRIMARIES_SCALE)); }; - if (m_settings.icc.fd >= 0) - m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length); + // FIXME: + // if (m_icc.fd >= 0) + // m_resource->sendIccFile(m_icc.fd, m_icc.length); // send preferred client paramateres m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x), diff --git a/src/protocols/ColorManagement.hpp b/src/protocols/ColorManagement.hpp index d43d5c12..7cdab37d 100644 --- a/src/protocols/ColorManagement.hpp +++ b/src/protocols/ColorManagement.hpp @@ -7,7 +7,7 @@ #include "../helpers/Monitor.hpp" #include "core/Compositor.hpp" #include "color-management-v1.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" class CColorManager; class CColorManagementOutput; @@ -109,6 +109,14 @@ class CColorManagementIccCreator { WP m_self; NColorManagement::SImageDescription m_settings; + struct SIccFile { + int fd = -1; + uint32_t length = 0; + uint32_t offset = 0; + bool operator==(const SIccFile& i2) const { + return fd == i2.fd; + } + } m_icc; private: SP m_resource; diff --git a/src/protocols/GammaControl.cpp b/src/protocols/GammaControl.cpp index 2517c754..c28b881f 100644 --- a/src/protocols/GammaControl.cpp +++ b/src/protocols/GammaControl.cpp @@ -56,6 +56,12 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out LOGM(Log::DEBUG, "setGamma for {}", m_monitor->m_name); + if UNLIKELY (m_monitor->gammaRampsInUse()) { + LOGM(Log::ERR, "Monitor has gamma ramps in use (ICC?)"); + m_resource->sendFailed(); + return; + } + // TODO: make CFileDescriptor getflags use F_GETFL int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0); if UNLIKELY (fdFlags < 0) { diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index b5357520..37ca51b7 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -20,7 +20,7 @@ #include "../../helpers/math/Math.hpp" #include "../../helpers/time/Time.hpp" #include "../types/Buffer.hpp" -#include "../types/ColorManagement.hpp" +#include "../../helpers/cm/ColorManagement.hpp" #include "../types/SurfaceRole.hpp" #include "../types/SurfaceState.hpp" diff --git a/src/protocols/types/ColorManagement.cpp b/src/protocols/types/ColorManagement.cpp deleted file mode 100644 index 5d23d1c9..00000000 --- a/src/protocols/types/ColorManagement.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "ColorManagement.hpp" -#include "../../macros.hpp" -#include -#include -#include - -namespace NColorManagement { - // expected to be small - static std::vector> knownPrimaries; - static std::vector> knownDescriptions; - static std::map, Hyprgraphics::CMatrix3> primariesConversion; - - const SPCPRimaries& getPrimaries(ePrimaries name) { - switch (name) { - case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709; - case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020; - case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M; - case CM_PRIMARIES_PAL: return NColorPrimaries::PAL; - case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC; - case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM; - case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ; - case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3; - case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3; - case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB; - default: return NColorPrimaries::DEFAULT_PRIMARIES; - } - } - - CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint primariesId) : m_id(primariesId), m_primaries(primaries) { - m_primaries2XYZ = m_primaries.toXYZ(); - } - - WP CPrimaries::from(const SPCPRimaries& primaries) { - for (const auto& known : knownPrimaries) { - if (known->value() == primaries) - return known; - } - - knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1))); - return knownPrimaries.back(); - } - - WP CPrimaries::from(const ePrimaries name) { - return from(getPrimaries(name)); - } - - WP CPrimaries::from(const uint primariesId) { - ASSERT(primariesId <= knownPrimaries.size()); - return knownPrimaries[primariesId - 1]; - } - - const SPCPRimaries& CPrimaries::value() const { - return m_primaries; - } - - uint CPrimaries::id() const { - return m_id; - } - - const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const { - return m_primaries2XYZ; - } - - const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP dst) const { - const auto cacheKey = std::make_pair(m_id, dst->m_id); - if (!primariesConversion.contains(cacheKey)) - primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries))); - - return primariesConversion[cacheKey]; - } - - CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId) : - m_id(imageDescriptionId), m_imageDescription(imageDescription) { - m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id(); - } - - PImageDescription CImageDescription::from(const SImageDescription& imageDescription) { - for (const auto& known : knownDescriptions) { - if (known->value() == imageDescription) - return known; - } - - knownDescriptions.emplace_back(CUniquePointer(new CImageDescription(imageDescription, knownDescriptions.size() + 1))); - return knownDescriptions.back(); - } - - PImageDescription CImageDescription::from(const uint imageDescriptionId) { - ASSERT(imageDescriptionId <= knownDescriptions.size()); - return knownDescriptions[imageDescriptionId - 1]; - } - - PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const { - auto desc = m_imageDescription; - desc.luminances = luminances; - return CImageDescription::from(desc); - } - - const SImageDescription& CImageDescription::value() const { - return m_imageDescription; - } - - uint CImageDescription::id() const { - return m_id; - } - - WP CImageDescription::getPrimaries() const { - return CPrimaries::from(m_primariesId); - } - -} \ No newline at end of file diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 916091db..b31a0e15 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -19,7 +19,7 @@ #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/ColorManagement.hpp" -#include "../protocols/types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/CursorManager.hpp" @@ -28,6 +28,7 @@ #include "../helpers/MainLoopExecutor.hpp" #include "../i18n/Engine.hpp" #include "../event/EventBus.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" #include "pass/TexPassElement.hpp" @@ -747,12 +748,24 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb m_renderData.pCurrentMonData->mirrorSwapFB.addStencil(m_renderData.pCurrentMonData->stencilTex); } - if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty()) + const bool HAS_MIRROR_FB = m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated(); + const bool NEEDS_COPY_FB = needsACopyFB(m_renderData.pMonitor.lock()); + + if (HAS_MIRROR_FB && !NEEDS_COPY_FB) m_renderData.pCurrentMonData->monitorMirrorFB.release(); + else if (!HAS_MIRROR_FB && NEEDS_COPY_FB) + m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); m_renderData.transformDamage = true; - m_renderData.damage.set(damage_); - m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + if (HAS_MIRROR_FB != NEEDS_COPY_FB) { + // force full damage because the mirror fb will be empty + m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX}); + m_renderData.finalDamage.set(m_renderData.damage); + } else { + m_renderData.damage.set(damage_); + m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + } m_fakeFrame = fb; @@ -777,6 +790,12 @@ void CHyprOpenGLImpl::end() { TRACY_GPU_ZONE("RenderEnd"); + m_renderData.currentWindow.reset(); + m_renderData.surface.reset(); + m_renderData.currentLS.reset(); + m_renderData.clipBox = {}; + m_renderData.clipRegion.clear(); + // end the render, copy the data to the main framebuffer if LIKELY (m_offloadedFramebuffer) { m_renderData.damage = m_renderData.finalDamage; @@ -793,17 +812,20 @@ void CHyprOpenGLImpl::end() { m_renderData.useNearestNeighbor = true; // copy the damaged areas into the mirror buffer - // we can't use the offloadFB for mirroring, as it contains artifacts from blurring - if UNLIKELY (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame) + // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring + if UNLIKELY (needsACopyFB(m_renderData.pMonitor.lock()) && !m_fakeFrame) saveBufferForMirror(monbox); m_renderData.outFB->bind(); blend(false); - if LIKELY (m_finalScreenShader->program() < 1 && !g_pHyprRenderer->m_crashingInProgress) + const auto PRIMITIVE_BLOCKED = + m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress || m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{}; + + if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL) renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox); - else - renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {}); + else // we need to use renderTexture if we do any CM whatsoever. + renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {.finalMonitorCM = true}); blend(true); @@ -849,6 +871,10 @@ void CHyprOpenGLImpl::end() { } } +bool CHyprOpenGLImpl::needsACopyFB(PHLMONITOR mon) { + return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon); +} + void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional finalDamage) { m_renderData.damage.set(damage_); m_renderData.finalDamage.set(finalDamage.value_or(damage_)); @@ -1054,7 +1080,7 @@ void CHyprOpenGLImpl::clear(const CHyprColor& color) { TRACY_GPU_ZONE("RenderClear"); - glClearColor(color.r, color.g, color.b, color.a); + GLCALL(glClearColor(color.r, color.g, color.b, color.a)); if (!m_renderData.damage.empty()) { m_renderData.damage.forEachRect([this](const auto& RECT) { @@ -1067,7 +1093,7 @@ void CHyprOpenGLImpl::clear(const CHyprColor& color) { void CHyprOpenGLImpl::blend(bool enabled) { if (enabled) { setCapStatus(GL_BLEND, true); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied + GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied } else setCapStatus(GL_BLEND, false); @@ -1086,7 +1112,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { box.transform(TR, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); if (box != m_lastScissorBox) { - glScissor(box.x, box.y, box.width, box.height); + GLCALL(glScissor(box.x, box.y, box.width, box.height)); m_lastScissorBox = box; } @@ -1095,7 +1121,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } if (originalBox != m_lastScissorBox) { - glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height); + GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height)); m_lastScissorBox = originalBox; } @@ -1288,22 +1314,42 @@ void CHyprOpenGLImpl::passCMUniforms(WP shader, const NColorManagement: const float maxLuminance = needsHDRmod ? imageDescription->value().getTFMaxLuminance(-1) : (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference); + shader->setUniformFloat(SHADER_MAX_LUMINANCE, maxLuminance * targetImageDescription->value().luminances.reference / imageDescription->value().luminances.reference); shader->setUniformFloat(SHADER_DST_MAX_LUMINANCE, targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000); shader->setUniformFloat(SHADER_SDR_SATURATION, needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f); shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f); - const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id()); - if (!primariesConversionCache.contains(cacheKey)) { - auto conversion = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries()); - const auto mat = conversion.mat(); - const std::array glConvertMatrix = { - mat[0][0], mat[1][0], mat[2][0], // - mat[0][1], mat[1][1], mat[2][1], // - mat[0][2], mat[1][2], mat[2][2], // - }; - primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + + if (!targetImageDescription->value().icc.present) { + const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id()); + + if (!primariesConversionCache.contains(cacheKey)) { + auto conversion = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries()); + const auto mat = conversion.mat(); + const std::array glConvertMatrix = { + mat[0][0], mat[1][0], mat[2][0], // + mat[0][1], mat[1][1], mat[2][1], // + mat[0][2], mat[1][2], mat[2][2], // + }; + primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + } + shader->setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); + + shader->setUniformInt(SHADER_USE_ICC, 0); + shader->setUniformInt(SHADER_LUT_3D, 8); // req'd for ogl + } else { + // ICC path, use a 3D LUT + shader->setUniformInt(SHADER_USE_ICC, 1); + + // TODO: this sucks + GLCALL(glActiveTexture(GL_TEXTURE8)); + targetImageDescription->value().icc.lutTexture->bind(); + + shader->setUniformInt(SHADER_LUT_3D, 8); + shader->setUniformFloat(SHADER_LUT_SIZE, targetImageDescription->value().icc.lutSize); + + GLCALL(glActiveTexture(GL_TEXTURE0)); } - shader->setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); } void CHyprOpenGLImpl::passCMUniforms(WP shader, const PImageDescription imageDescription) { @@ -1341,23 +1387,19 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c WP shader; - bool usingFinalShader = false; - - const bool CRASHING = m_applyFinalShader && g_pHyprRenderer->m_crashingInProgress; + const bool CRASHING = m_applyFinalShader && g_pHyprRenderer->m_crashingInProgress; + const bool CUSTOM_FINAL_SHADER = !CRASHING && m_applyFinalShader && m_finalScreenShader->program(); uint8_t shaderFeatures = 0; - if (CRASHING) { - shader = m_shaders->frag[SH_FRAG_GLITCH]; - usingFinalShader = true; - } else if (m_applyFinalShader && m_finalScreenShader->program()) { - shader = m_finalScreenShader; - usingFinalShader = true; - } else { - if (m_applyFinalShader) { - shader = m_shaders->frag[SH_FRAG_PASSTHRURGBA]; - usingFinalShader = true; - } else { + if (CRASHING) + shader = m_shaders->frag[SH_FRAG_GLITCH]; + else if (CUSTOM_FINAL_SHADER) + shader = m_finalScreenShader; + else { + if (m_applyFinalShader) + shaderFeatures &= ~SH_FEAT_RGBA; + else { switch (tex->m_type) { case TEXTURE_RGBA: shaderFeatures |= SH_FEAT_RGBA; break; case TEXTURE_RGBX: shaderFeatures &= ~SH_FEAT_RGBA; break; @@ -1368,7 +1410,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c } } - if (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault()) + if (data.finalMonitorCM || (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault())) shaderFeatures &= ~SH_FEAT_RGBA; glActiveTexture(GL_TEXTURE0); @@ -1388,18 +1430,60 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c const bool isHDRSurface = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? m_renderData.surface->m_colorManagement->isHDR() : false; const bool canPassHDRSurface = isHDRSurface && !m_renderData.surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader - const auto imageDescription = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? - CImageDescription::from(m_renderData.surface->m_colorManagement->imageDescription()) : - (data.cmBackToSRGB ? data.cmBackToSRGBSource->m_imageDescription : DEFAULT_IMAGE_DESCRIPTION); + // Color pipeline: + // Client ---> sRGB chosen EOTF (render buf) ---> Monitor (target) + // - const auto sdrEOTF = NTransferFunction::fromConfig(); - auto chosenSdrEotf = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; - const auto targetImageDescription = - data.cmBackToSRGB ? CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}) : m_renderData.pMonitor->m_imageDescription; + const auto CONFIG_SDR_EOTF = NTransferFunction::fromConfig(); + const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present; + const auto CHOSEN_SDR_EOTF = [&] { + // if the monitor is ICC'd, use SRGB for best ΔE. + if (IS_MONITOR_ICC) + return CM_TRANSFER_FUNCTION_SRGB; - const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ - || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ - || (imageDescription->id() == targetImageDescription->id() && !data.cmBackToSRGB) /* Source and target have the same image description */ + // otherwise use configured + if (CONFIG_SDR_EOTF != NTransferFunction::TF_SRGB) + return CM_TRANSFER_FUNCTION_GAMMA22; + return CM_TRANSFER_FUNCTION_SRGB; + }(); + const auto WORK_BUFFER_IMAGE_DESCRIPTION = CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); + + // chosenSdrEotf contains the valid eotf for this display + + const auto SOURCE_IMAGE_DESCRIPTION = [&] { + // if valid CM surface, use that as a source + if (m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid()) + return CImageDescription::from(m_renderData.surface->m_colorManagement->imageDescription()); + + // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in + // the same applies to the final monitor CM + if (data.cmBackToSRGB || data.finalMonitorCM) // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + + // otherwise, default + return DEFAULT_IMAGE_DESCRIPTION; + }(); + + const auto TARGET_IMAGE_DESCRIPTION = [&] { + // if we are CM'ing back, use default sRGB + if (data.cmBackToSRGB) + return DEFAULT_IMAGE_DESCRIPTION; + + // for final CM, use the target description + if (data.finalMonitorCM) + return m_renderData.pMonitor->m_imageDescription; + + // otherwise, use chosen, we're drawing into the work buffer + // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + }(); + + const bool CANT_CHECK_CM_EQUALITY = data.cmBackToSRGB || data.finalMonitorCM || (!m_renderData.surface || !m_renderData.surface->m_colorManagement); + + const bool skipCM = data.noCM /* manual CM disable */ + || !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ + || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ + || (SOURCE_IMAGE_DESCRIPTION->id() == TARGET_IMAGE_DESCRIPTION->id() && !CANT_CHECK_CM_EQUALITY) /* Source and target have the same image description */ || (((*PPASS && canPassHDRSurface) || (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) && m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; @@ -1407,33 +1491,32 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c if (data.discardActive) shaderFeatures |= SH_FEAT_DISCARD; - if (!usingFinalShader) { - if (data.allowDim && m_renderData.currentWindow && (m_renderData.currentWindow->m_notRespondingTint->value() > 0 || m_renderData.currentWindow->m_dimPercent->value() > 0)) - shaderFeatures |= SH_FEAT_TINT; + if (data.allowDim && m_renderData.currentWindow && (m_renderData.currentWindow->m_notRespondingTint->value() > 0 || m_renderData.currentWindow->m_dimPercent->value() > 0)) + shaderFeatures |= SH_FEAT_TINT; - if (data.round > 0) - shaderFeatures |= SH_FEAT_ROUNDING; + if (data.round > 0) + shaderFeatures |= SH_FEAT_ROUNDING; - if (!skipCM) { - shaderFeatures |= SH_FEAT_CM; + if (!skipCM) { + shaderFeatures |= SH_FEAT_CM; - const bool needsSDRmod = isSDR2HDR(imageDescription->value(), targetImageDescription->value()); - const bool needsHDRmod = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value()); - const float maxLuminance = needsHDRmod ? - imageDescription->value().getTFMaxLuminance(-1) : - (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference); - const auto dstMaxLuminance = targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000; + const bool needsSDRmod = isSDR2HDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const bool needsHDRmod = !needsSDRmod && isHDR2SDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const float maxLuminance = needsHDRmod ? + SOURCE_IMAGE_DESCRIPTION->value().getTFMaxLuminance(-1) : + (SOURCE_IMAGE_DESCRIPTION->value().luminances.max > 0 ? SOURCE_IMAGE_DESCRIPTION->value().luminances.max : SOURCE_IMAGE_DESCRIPTION->value().luminances.reference); + const auto dstMaxLuminance = TARGET_IMAGE_DESCRIPTION->value().luminances.max > 0 ? TARGET_IMAGE_DESCRIPTION->value().luminances.max : 10000; - if (maxLuminance >= dstMaxLuminance * 1.01) - shaderFeatures |= SH_FEAT_TONEMAP; + if (maxLuminance >= dstMaxLuminance * 1.01) + shaderFeatures |= SH_FEAT_TONEMAP; - if (!data.cmBackToSRGB && - (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && - targetImageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && - ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || - (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f))) - shaderFeatures |= SH_FEAT_SDR_MOD; - } + if (data.finalMonitorCM && + (SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || + SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && + TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && + ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || + (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f))) + shaderFeatures |= SH_FEAT_SDR_MOD; } if (!shader) @@ -1441,22 +1524,22 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader = useShader(shader); - if (!skipCM && !usingFinalShader) { - if (data.cmBackToSRGB) - passCMUniforms(shader, imageDescription, targetImageDescription, true, -1, -1); + if (!skipCM) { + if (data.finalMonitorCM) + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); else - passCMUniforms(shader, imageDescription); + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, -1, -1); } shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); - if ((usingFinalShader && *PDT == 0) || CRASHING) + if ((CUSTOM_FINAL_SHADER && *PDT == 0) || CRASHING) shader->setUniformFloat(SHADER_TIME, m_globalTimer.getSeconds() - shader->getInitialTime()); - else if (usingFinalShader) - shader->setUniformFloat(SHADER_TIME, 0.f); + else if (CUSTOM_FINAL_SHADER) + shader->setUniformFloat(SHADER_TIME, 0.F); - if (usingFinalShader) { + if (CUSTOM_FINAL_SHADER) { shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id); shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT); @@ -1467,7 +1550,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize()); } - if (usingFinalShader && *PDT == 0) { + if (CUSTOM_FINAL_SHADER && *PDT == 0) { PHLMONITORREF pMonitor = m_renderData.pMonitor; Vector2D p = ((g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position) * pMonitor->m_scale); p = p.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); @@ -1493,7 +1576,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds()); shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds()); - } else if (usingFinalShader) { + } else if (CUSTOM_FINAL_SHADER) { shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f); static const std::vector pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f); @@ -1512,17 +1595,15 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); } - if (!usingFinalShader) { - shader->setUniformFloat(SHADER_ALPHA, alpha); + shader->setUniformFloat(SHADER_ALPHA, alpha); - if (data.discardActive) { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); - shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); - shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); - } else { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); - shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); - } + if (data.discardActive) { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); + shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); + shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); + } else { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); + shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); } CBox transformedBox = newBox; @@ -1532,27 +1613,25 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); - if (!usingFinalShader) { - // Rounded corners - shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); - shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); - shader->setUniformFloat(SHADER_RADIUS, data.round); - shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + // Rounded corners + shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); + shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); + shader->setUniformFloat(SHADER_RADIUS, data.round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - if (data.allowDim && m_renderData.currentWindow) { - if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { - const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); - shader->setUniformInt(SHADER_APPLY_TINT, 1); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { - shader->setUniformInt(SHADER_APPLY_TINT, 1); - const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else - shader->setUniformInt(SHADER_APPLY_TINT, 0); + if (data.allowDim && m_renderData.currentWindow) { + if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { + const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); + shader->setUniformInt(SHADER_APPLY_TINT, 1); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); + } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { + shader->setUniformInt(SHADER_APPLY_TINT, 1); + const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); } else shader->setUniformInt(SHADER_APPLY_TINT, 0); - } + } else + shader->setUniformInt(SHADER_APPLY_TINT, 0); glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); glBindBuffer(GL_ARRAY_BUFFER, shader->getUniformLocation(SHADER_SHADER_VBO)); @@ -1605,8 +1684,8 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c }); } - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + GLCALL(glBindVertexArray(0)); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); tex->unbind(); } @@ -1759,24 +1838,7 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi WP shader; - // From FB to sRGB - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - if (!skipCM) { - shader = useShader(m_shaders->frag[SH_FRAG_CM_BLURPREPARE]); - passCMUniforms(shader, m_renderData.pMonitor->m_imageDescription, DEFAULT_IMAGE_DESCRIPTION); - shader->setUniformFloat(SHADER_SDR_SATURATION, - m_renderData.pMonitor->m_sdrSaturation > 0 && - m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrSaturation : - 1.0f); - shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, - m_renderData.pMonitor->m_sdrBrightness > 0 && - m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrBrightness : - 1.0f); - } else - shader = useShader(m_shaders->frag[SH_FRAG_BLURPREPARE]); - + shader = useShader(m_shaders->frag[SH_FRAG_BLURPREPARE]); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); @@ -2238,14 +2300,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - WP shader; - - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - if (!skipCM) { - shader = useShader(m_shaders->frag[SH_FRAG_CM_BORDER1]); - passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); - } else - shader = useShader(m_shaders->frag[SH_FRAG_BORDER1]); + WP shader = useShader(m_shaders->frag[SH_FRAG_BORDER1]); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); @@ -2324,13 +2379,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - WP shader; - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - if (!skipCM) { - shader = useShader(m_shaders->frag[SH_FRAG_CM_BORDER1]); - passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); - } else - shader = useShader(m_shaders->frag[SH_FRAG_BORDER1]); + WP shader = useShader(m_shaders->frag[SH_FRAG_BORDER1]); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA); @@ -2403,11 +2452,7 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun blend(true); - auto shader = useShader(m_shaders->frag[SH_FRAG_SHADOW]); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - shader->setUniformInt(SHADER_SKIP_CM, skipCM); - if (!skipCM) - passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + auto shader = useShader(m_shaders->frag[SH_FRAG_SHADOW]); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); @@ -2448,21 +2493,17 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun } void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { - - if (!m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated()) - m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->monitorMirrorFB.bind(); blend(false); renderTexture(m_renderData.currentFB->getTexture(), box, STextureRenderData{ - .a = 1.f, + .a = 1.F, .round = 0, .discardActive = false, .allowCustomUV = false, + .cmBackToSRGB = true, }); blend(true); @@ -3050,9 +3091,9 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (idx == CAP_STATUS_END) { if (status) - glEnable(cap); + GLCALL(glEnable(cap)) else - glDisable(cap); + GLCALL(glDisable(cap)); return; } @@ -3062,10 +3103,10 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (status) { m_capStatus[idx] = status; - glEnable(cap); + GLCALL(glEnable(cap)); } else { m_capStatus[idx] = status; - glDisable(cap); + GLCALL(glDisable(cap)); } } diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index bc1f5f4d..d801d40b 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -142,7 +142,7 @@ struct SMonitorRenderData { CFramebuffer mirrorFB; // these are used for some effects, CFramebuffer mirrorSwapFB; // etc CFramebuffer offMainFB; - CFramebuffer monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB + CFramebuffer monitorMirrorFB; // used for mirroring outputs / screencopy, does not contain artifacts like offloadFB and is in sRGB CFramebuffer blurFB; SP stencilTex = makeShared(); @@ -239,8 +239,9 @@ class CHyprOpenGLImpl { bool noAA = false; bool blockBlurOptimization = false; GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; - bool cmBackToSRGB = false; - SP cmBackToSRGBSource; + bool cmBackToSRGB = false; + bool noCM = false; + bool finalMonitorCM = false; }; struct SBorderRenderData { @@ -261,6 +262,7 @@ class CHyprOpenGLImpl { void renderBorder(const CBox&, const CGradientValueData&, SBorderRenderData data); void renderBorder(const CBox&, const CGradientValueData&, const CGradientValueData&, float lerp, SBorderRenderData data); void renderTextureMatte(SP tex, const CBox& pBox, CFramebuffer& matte); + void renderTexturePrimitive(SP tex, const CBox& box); void pushMonitorTransformEnabled(bool enabled); void popMonitorTransformEnabled(); @@ -300,6 +302,8 @@ class CHyprOpenGLImpl { void renderOffToMain(CFramebuffer* off); void bindBackOnMain(); + bool needsACopyFB(PHLMONITOR mon); + std::string resolveAssetPath(const std::string& file); SP loadAsset(const std::string& file); SP texFromCairo(cairo_surface_t* cairo); @@ -446,7 +450,6 @@ class CHyprOpenGLImpl { void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription); - void renderTexturePrimitive(SP tex, const CBox& box); void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size); void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 5f62232c..5f18b906 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -127,7 +127,6 @@ void CShader::getUniformLocations() { m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType"); // shader has #include "CM.glsl" - m_uniformLocations[SHADER_SKIP_CM] = getUniform("skipCM"); m_uniformLocations[SHADER_SOURCE_TF] = getUniform("sourceTF"); m_uniformLocations[SHADER_TARGET_TF] = getUniform("targetTF"); m_uniformLocations[SHADER_SRC_TF_RANGE] = getUniform("srcTFRange"); @@ -140,6 +139,9 @@ void CShader::getUniformLocations() { m_uniformLocations[SHADER_SDR_SATURATION] = getUniform("sdrSaturation"); m_uniformLocations[SHADER_SDR_BRIGHTNESS] = getUniform("sdrBrightnessMultiplier"); m_uniformLocations[SHADER_CONVERT_MATRIX] = getUniform("convertMatrix"); + m_uniformLocations[SHADER_USE_ICC] = getUniform("useIcc"); + m_uniformLocations[SHADER_LUT_3D] = getUniform("iccLut3D"); + m_uniformLocations[SHADER_LUT_SIZE] = getUniform("iccLutSize"); // m_uniformLocations[SHADER_TEX] = getUniform("tex"); m_uniformLocations[SHADER_ALPHA] = getUniform("alpha"); @@ -248,7 +250,8 @@ void CShader::setUniformInt(eShaderUniform location, GLint v0) { return; cached = v0; - glUniform1i(m_uniformLocations[location], v0); + + GLCALL(glUniform1i(m_uniformLocations[location], v0)); } void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { @@ -264,7 +267,7 @@ void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { } cached = v0; - glUniform1f(m_uniformLocations[location], v0); + GLCALL(glUniform1f(m_uniformLocations[location], v0)); } void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { @@ -280,7 +283,7 @@ void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) } cached = std::array{v0, v1}; - glUniform2f(m_uniformLocations[location], v0, v1); + GLCALL(glUniform2f(m_uniformLocations[location], v0, v1)); } void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { @@ -296,7 +299,7 @@ void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2}; - glUniform3f(m_uniformLocations[location], v0, v1, v2); + GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2)); } void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { @@ -312,7 +315,7 @@ void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2, v3}; - glUniform4f(m_uniformLocations[location], v0, v1, v2, v3); + GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3)); } void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { @@ -328,7 +331,7 @@ void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLbool } cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data())); } void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { @@ -344,7 +347,7 @@ void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLbo } cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data())); } void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { @@ -361,9 +364,9 @@ void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::ve cached = SUniformVData{.count = count, .value = value}; switch (vec_size) { - case 1: glUniform1fv(m_uniformLocations[location], count, value.data()); break; - case 2: glUniform2fv(m_uniformLocations[location], count, value.data()); break; - case 4: glUniform4fv(m_uniformLocations[location], count, value.data()); break; + case 1: GLCALL(glUniform1fv(m_uniformLocations[location], count, value.data())); break; + case 2: GLCALL(glUniform2fv(m_uniformLocations[location], count, value.data())); break; + case 4: GLCALL(glUniform4fv(m_uniformLocations[location], count, value.data())); break; default: UNREACHABLE(); } } diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 9f871c0e..184f6771 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -9,7 +9,6 @@ enum eShaderUniform : uint8_t { SHADER_COLOR, SHADER_ALPHA_MATTE, SHADER_TEX_TYPE, - SHADER_SKIP_CM, SHADER_SOURCE_TF, SHADER_TARGET_TF, SHADER_SRC_TF_RANGE, @@ -75,6 +74,9 @@ enum eShaderUniform : uint8_t { SHADER_POINTER_INACTIVE_TIMEOUT, SHADER_POINTER_LAST_ACTIVE, SHADER_POINTER_SIZE, + SHADER_USE_ICC, + SHADER_LUT_3D, + SHADER_LUT_SIZE, SHADER_LAST, }; diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp index 0e807485..1a35e488 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -58,6 +58,32 @@ CTexture::CTexture(const SP buffer, bool keepDataCopy) : m_ createFromDma(attrs, image); } +CTexture::CTexture(std::span lut3D, size_t N) : m_type(TEXTURE_3D_LUT), m_target(GL_TEXTURE_3D), m_size(lut3D.size() / 3, 1), m_isSynchronous(true) { + allocate(); + bind(); + + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Expand RGB->RGBA on upload (alpha=1) + std::vector rgba; + rgba.resize(N * N * N * 4); + for (size_t i = 0, j = 0; i < N * N * N; ++i, j += 3) { + rgba[i * 4 + 0] = lut3D[j + 0]; + rgba[i * 4 + 1] = lut3D[j + 1]; + rgba[i * 4 + 2] = lut3D[j + 2]; + rgba[i * 4 + 3] = 1.F; + } + + GLCALL(glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, N, N, N, 0, GL_RGBA, GL_FLOAT, rgba.data())); + + unbind(); +} + void CTexture::createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_) { g_pHyprRenderer->makeEGLCurrent(); diff --git a/src/render/Texture.hpp b/src/render/Texture.hpp index c2e9b2c3..a5806e26 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include #include +#include class IHLBuffer; HYPRUTILS_FORWARD(Math, CRegion); @@ -11,6 +12,7 @@ enum eTextureType : int8_t { TEXTURE_INVALID = -1, // Invalid TEXTURE_RGBA = 0, // 4 channels TEXTURE_RGBX, // discard A + TEXTURE_3D_LUT, // 3D LUT TEXTURE_EXTERNAL, // EGLImage }; @@ -24,6 +26,7 @@ class CTexture { CTexture(const CTexture&) = delete; CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false); + CTexture(std::span lut3D, size_t N); CTexture(const SP buffer, bool keepDataCopy = false); // this ctor takes ownership of the eglImage. diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl index 36c95a90..66d84885 100644 --- a/src/render/shaders/glsl/CM.glsl +++ b/src/render/shaders/glsl/CM.glsl @@ -6,6 +6,10 @@ uniform mat3 convertMatrix; #include "sdr_mod.glsl" +uniform int useIcc; +uniform highp sampler3D iccLut3D; +uniform float iccLutSize; + //enum eTransferFunction #define CM_TRANSFER_FUNCTION_BT1886 1 #define CM_TRANSFER_FUNCTION_GAMMA22 2 @@ -65,6 +69,24 @@ uniform mat3 convertMatrix; #define M_E 2.718281828459045 + +vec3 applyIcc3DLut(vec3 linearRgb01) { + vec3 x = clamp(linearRgb01, 0.0, 1.0); + + // Map [0..1] to texel centers to avoid edge issues + float N = iccLutSize; + vec3 coord = (x * (N - 1.0) + 0.5) / N; + + return texture(iccLut3D, coord).rgb; +} + +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); +} + // The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf vec3 tfInvPQ(vec3 color) { vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); @@ -250,7 +272,7 @@ vec4 fromLinear(vec4 color, int tf) { return color; } -vec4 fromLinearNit(vec4 color, int tf, vec2 range) { +vec4 fromLinearNit(vec4 color, int tf, vec2 range) { if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) color.rgb = color.rgb / SDR_MAX_LUMINANCE; else { @@ -267,12 +289,18 @@ vec4 fromLinearNit(vec4 color, int tf, vec2 range) { vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat3 dstxyz) { pixColor.rgb /= max(pixColor.a, 0.001); pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); - pixColor.rgb = convertMatrix * pixColor.rgb; - pixColor = toNit(pixColor, srcTFRange); - pixColor.rgb *= pixColor.a; - #include "do_tonemap.glsl" - pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); - #include "do_sdr_mod.glsl" + + if (useIcc == 1) { + pixColor.rgb = applyIcc3DLut(pixColor.rgb); + pixColor.rgb *= pixColor.a; + } else { + pixColor.rgb = convertMatrix * pixColor.rgb; + pixColor = toNit(pixColor, srcTFRange); + pixColor.rgb *= pixColor.a; + #include "do_tonemap.glsl" + pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); + #include "do_sdr_mod.glsl" + } return pixColor; } diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index 71e96ddb..06aa605c 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -5,7 +5,6 @@ precision highp float; in vec4 v_color; in vec2 v_texcoord; -uniform int skipCM; uniform int sourceTF; // eTransferFunction uniform int targetTF; // eTransferFunction uniform mat3 targetPrimariesXYZ; @@ -18,8 +17,6 @@ uniform float roundingPower; uniform float range; uniform float shadowPower; -#include "CM.glsl" - float pixAlphaRoundedDistance(float distanceToCorner) { if (distanceToCorner > radius) { return 0.0; @@ -92,8 +89,5 @@ void main() { // premultiply pixColor.rgb *= pixColor[3]; - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimariesXYZ); - fragColor = pixColor; } \ No newline at end of file