render/cm: add ICC profile pipeline (#12711)
Adds an ICC profile pipeline, loading via config and applying via 3D LUTs.
This commit is contained in:
parent
8271cfc97b
commit
10754745a9
27 changed files with 984 additions and 373 deletions
|
|
@ -266,7 +266,8 @@ pkg_check_modules(
|
|||
gbm
|
||||
gio-2.0
|
||||
re2
|
||||
muparser)
|
||||
muparser
|
||||
lcms2)
|
||||
|
||||
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <aquamarine/backend/Backend.hpp>
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
|
|||
|
|
@ -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<std::string> CConfigManager::handleMonitorv2(const std::string& ou
|
|||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());
|
||||
|
||||
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str());
|
||||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().iccFile = std::any_cast<Hyprlang::STRING>(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<std::string> 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]));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<uint16_t> resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) {
|
||||
std::vector<uint16_t> 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<float>(i0);
|
||||
|
||||
const float v0 = sc<float>(t.ch[c][i0]);
|
||||
const float v1 = sc<float>(t.ch[c][i1]);
|
||||
const float v = v0 + ((v1 - v0) * f);
|
||||
|
||||
int64_t vi = std::round(v);
|
||||
vi = std::clamp(vi, sc<int64_t>(0), sc<int64_t>(65535));
|
||||
return sc<uint16_t>(vi);
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < gammaSize; ++i) {
|
||||
float x = sc<float>(i) * sc<float>(t.entries - 1) / sc<float>(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) {
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#include "math/Math.hpp"
|
||||
#include "../desktop/reserved/ReservedArea.hpp"
|
||||
#include <optional>
|
||||
#include "../protocols/types/ColorManagement.hpp"
|
||||
#include "cm/ColorManagement.hpp"
|
||||
#include "signal/Signal.hpp"
|
||||
#include "DamageRing.hpp"
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
@ -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<WORKSPACEID> m_prevWorkSpaces;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
193
src/helpers/cm/ColorManagement.cpp
Normal file
193
src/helpers/cm/ColorManagement.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#include "ColorManagement.hpp"
|
||||
#include "../../macros.hpp"
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
using namespace NColorManagement;
|
||||
|
||||
namespace NColorManagement {
|
||||
// expected to be small
|
||||
static std::vector<UP<const CPrimaries>> knownPrimaries;
|
||||
static std::vector<UP<const CImageDescription>> knownDescriptions;
|
||||
static std::map<std::pair<uint, uint>, 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<const CPrimaries> 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<const CPrimaries> CPrimaries::from(const ePrimaries name) {
|
||||
return from(getPrimaries(name));
|
||||
}
|
||||
|
||||
WP<const CPrimaries> 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<const CPrimaries> 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<CImageDescription>(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<const CPrimaries> CImageDescription::getPrimaries() const {
|
||||
return CPrimaries::from(m_primariesId);
|
||||
}
|
||||
|
||||
static Mat3x3 diag3(const std::array<float, 3>& s) {
|
||||
return Mat3x3{std::array<float, 9>{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}};
|
||||
}
|
||||
|
||||
static std::optional<Mat3x3> 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<float, 9>{
|
||||
A * invDet,
|
||||
D * invDet,
|
||||
G * invDet, //
|
||||
B * invDet,
|
||||
E * invDet,
|
||||
H * invDet, //
|
||||
C * invDet,
|
||||
F * invDet,
|
||||
I * invDet, //
|
||||
}};
|
||||
return inv;
|
||||
}
|
||||
|
||||
static std::array<float, 3> matByVec(const Mat3x3& M, const std::array<float, 3>& 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<Mat3x3> 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<float, 9>{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<float, 9>{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<float, 3> scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]};
|
||||
|
||||
Mat3x3 result = BradfordInv;
|
||||
result.multiply(diag3(scale)).multiply(Bradford);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -3,6 +3,11 @@
|
|||
#include "color-management-v1.hpp"
|
||||
#include <hyprgraphics/color/Color.hpp>
|
||||
#include "../../helpers/memory/Memory.hpp"
|
||||
#include "../../helpers/math/Math.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <expected>
|
||||
|
||||
#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<std::vector<uint16_t>, 3> ch;
|
||||
};
|
||||
|
||||
const SPCPRimaries& getPrimaries(ePrimaries name);
|
||||
std::optional<Mat3x3> 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<SImageDescription, std::string> fromICC(const std::filesystem::path& file);
|
||||
|
||||
bool windowsScRGB = false;
|
||||
//
|
||||
std::vector<uint8_t> 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<float> lutDataPacked;
|
||||
SP<CTexture> lutTexture;
|
||||
std::optional<SVCGTTable16> 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<const CImageDescription> from(const SImageDescription& imageDescription);
|
||||
static WP<const CImageDescription> from(const uint imageDescriptionId);
|
||||
static WP<const CImageDescription> from(const uint32_t imageDescriptionId);
|
||||
|
||||
WP<const CImageDescription> with(const SImageDescription::SPCLuminances& luminances) const;
|
||||
|
||||
const SImageDescription& value() const;
|
||||
uint id() const;
|
||||
uint32_t id() const;
|
||||
|
||||
WP<const CPrimaries> 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,
|
||||
278
src/helpers/cm/ICC.cpp
Normal file
278
src/helpers/cm/ICC.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#include "ColorManagement.hpp"
|
||||
#include "../math/Math.hpp"
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
|
||||
#include "../../debug/log/Logger.hpp"
|
||||
#include "../../render/Texture.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
using namespace Hyprutils::Utils;
|
||||
|
||||
#include <lcms2.h>
|
||||
|
||||
using namespace NColorManagement;
|
||||
|
||||
static std::vector<uint8_t> 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<uint8_t> buf;
|
||||
buf.resize(len);
|
||||
ifs.read(reinterpret_cast<char*>(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<cmsTagSignature>(sc<uint32_t>(a) << 24 | sc<uint32_t>(b) << 16 | sc<uint32_t>(c) << 8 | sc<uint32_t>(d));
|
||||
}
|
||||
|
||||
static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't');
|
||||
|
||||
//
|
||||
|
||||
static std::expected<std::optional<SVCGTTable16>, 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<uint8_t> 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<ptrdiff_t>((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<std::remove_pointer_t<cmsHPROFILE>, CmsProfileDeleter>;
|
||||
using UniqueTransform = std::unique_ptr<std::remove_pointer_t<cmsHTRANSFORM>, 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<void, std::string> 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<CTexture>(image.icc.lutDataPacked, image.icc.lutSize);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<SImageDescription, std::string> SImageDescription::fromICC(const std::filesystem::path& file) {
|
||||
static auto PVCGTENABLED = CConfigValue<Hyprlang::INT>("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;
|
||||
}
|
||||
|
|
@ -597,7 +597,7 @@ SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager
|
|||
Log::logger->log(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();
|
||||
|
|
|
|||
|
|
@ -159,9 +159,11 @@ void CScreenshareFrame::renderMonitor() {
|
|||
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
auto TEXTURE = makeShared<CTexture>(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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CScreenshareSession>&& session) : m_session(std::move(session)) {
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ namespace Screenshare {
|
|||
void destroyClientSessions(wl_client* client);
|
||||
|
||||
void onOutputCommit(PHLMONITOR monitor);
|
||||
bool isOutputBeingSSd(PHLMONITOR monitor);
|
||||
|
||||
private:
|
||||
std::vector<WP<CScreenshareSession>> m_sessions;
|
||||
|
|
|
|||
|
|
@ -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 <cstdint>
|
||||
|
||||
using namespace NColorManagement;
|
||||
|
|
@ -388,12 +388,6 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP<CWpColorMana
|
|||
RESOURCE->m_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(SP<CWpImageDescriptionCre
|
|||
LOGM(Log::TRACE, "Create image description from icc for id {}", id);
|
||||
|
||||
// FIXME actually check completeness
|
||||
if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
|
||||
if (m_icc.fd < 0 || !m_icc.length) {
|
||||
r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings");
|
||||
return;
|
||||
}
|
||||
|
|
@ -443,10 +437,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
|
|||
return;
|
||||
}
|
||||
|
||||
LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_settings.icc.fd, m_settings.icc.offset, m_settings.icc.length, id);
|
||||
LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_icc.fd, m_icc.offset, m_icc.length, id);
|
||||
|
||||
// FIXME actually check support
|
||||
if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
|
||||
if (m_icc.fd < 0 || !m_icc.length) {
|
||||
RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported");
|
||||
return;
|
||||
}
|
||||
|
|
@ -459,9 +453,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
|
|||
});
|
||||
|
||||
m_resource->setSetIccFile([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<CW
|
|||
|
||||
const auto toProto = [](float value) { return sc<int32_t>(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),
|
||||
|
|
|
|||
|
|
@ -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<CColorManagementIccCreator> 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<CWpImageDescriptionCreatorIccV1> m_resource;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ CGammaControl::CGammaControl(SP<CZwlrGammaControlV1> 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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
#include "ColorManagement.hpp"
|
||||
#include "../../macros.hpp"
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace NColorManagement {
|
||||
// expected to be small
|
||||
static std::vector<UP<const CPrimaries>> knownPrimaries;
|
||||
static std::vector<UP<const CImageDescription>> knownDescriptions;
|
||||
static std::map<std::pair<uint, uint>, 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<const CPrimaries> 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<const CPrimaries> CPrimaries::from(const ePrimaries name) {
|
||||
return from(getPrimaries(name));
|
||||
}
|
||||
|
||||
WP<const CPrimaries> 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<const CPrimaries> 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<const CPrimaries> CImageDescription::getPrimaries() const {
|
||||
return CPrimaries::from(m_primariesId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<CRegion> 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<CShader> 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<GLfloat, 9> 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<GLfloat, 9> 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<CShader> shader, const PImageDescription imageDescription) {
|
||||
|
|
@ -1341,23 +1387,19 @@ void CHyprOpenGLImpl::renderTextureInternal(SP<CTexture> tex, const CBox& box, c
|
|||
|
||||
WP<CShader> 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<CTexture> 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<CTexture> 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<CTexture> 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<CTexture> 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<CTexture> 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<CTexture> 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<float> pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f);
|
||||
|
|
@ -1512,17 +1595,15 @@ void CHyprOpenGLImpl::renderTextureInternal(SP<CTexture> 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<CTexture> 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<CTexture> 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<CShader> 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<CShader> 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<CShader> 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<CShader> 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<CShader> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CTexture> stencilTex = makeShared<CTexture>();
|
||||
|
|
@ -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<CMonitor> 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<CTexture> tex, const CBox& pBox, CFramebuffer& matte);
|
||||
void renderTexturePrimitive(SP<CTexture> 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<CTexture> loadAsset(const std::string& file);
|
||||
SP<CTexture> texFromCairo(cairo_surface_t* cairo);
|
||||
|
|
@ -446,7 +450,6 @@ class CHyprOpenGLImpl {
|
|||
void passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
|
||||
bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1);
|
||||
void passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription);
|
||||
void renderTexturePrimitive(SP<CTexture> 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);
|
||||
|
|
|
|||
|
|
@ -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<GLfloat, 2>{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<GLfloat, 3>{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<GLfloat, 4>{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<GLfloat, 9> 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<GLfloat, 8> 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<float>& 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,6 +58,32 @@ CTexture::CTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy) : m_
|
|||
createFromDma(attrs, image);
|
||||
}
|
||||
|
||||
CTexture::CTexture(std::span<const float> 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<float> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "../defines.hpp"
|
||||
#include <aquamarine/buffer/Buffer.hpp>
|
||||
#include <hyprutils/math/Misc.hpp>
|
||||
#include <span>
|
||||
|
||||
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<const float> lut3D, size_t N);
|
||||
|
||||
CTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy = false);
|
||||
// this ctor takes ownership of the eglImage.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue