Skip to content

Commit 4187034

Browse files
committed
add lut ramps
1 parent 85fa77d commit 4187034

File tree

7 files changed

+126
-48
lines changed

7 files changed

+126
-48
lines changed

src/helpers/cm/ColorManagement.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#define HDR_REF_LUMINANCE 203.0
1717
#define HLG_MAX_LUMINANCE 1000.0
1818

19+
class CTexture;
20+
1921
namespace NColorManagement {
2022
enum eNoShader : uint8_t {
2123
CM_NS_DISABLE = 0,
@@ -175,6 +177,13 @@ namespace NColorManagement {
175177
}
176178
} masteringLuminances;
177179

180+
// LUT ramps replace destTF. These are used for ICC.
181+
// These are INVERTED, ready for linear -> dest
182+
struct SLUTRamps {
183+
bool present = false;
184+
SP<CTexture> r, g, b;
185+
} lutRamps;
186+
178187
uint32_t maxCLL = 0;
179188
uint32_t maxFALL = 0;
180189

src/helpers/cm/ICC.cpp

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <fstream>
55

66
#include "../../debug/log/Logger.hpp"
7+
#include "../../render/Texture.hpp"
8+
#include "../../render/Renderer.hpp"
79

810
#include <hyprutils/utils/ScopeGuard.hpp>
911
using namespace Hyprutils::Utils;
@@ -12,6 +14,9 @@ using namespace Hyprutils::Utils;
1214

1315
using namespace NColorManagement;
1416

17+
// IMPORTANT: this needs to match LUT_SIZE in CM.glsl
18+
constexpr const size_t LUT_SIZE = 2048;
19+
1520
static std::vector<uint8_t> readBinary(const std::filesystem::path& file) {
1621
std::ifstream ifs(file, std::ios::binary);
1722
if (!ifs.good())
@@ -118,21 +123,6 @@ static std::optional<Mat3x3> invertMat3(const Mat3x3& m) {
118123
return inv;
119124
}
120125

121-
static bool toneCurvesSimilar(cmsToneCurve* a, cmsToneCurve* b) {
122-
if (!a || !b)
123-
return false;
124-
125-
for (int i = 0; i <= 256; ++i) {
126-
double x = (double)i / 256.0;
127-
double ya = cmsEvalToneCurveFloat(a, x);
128-
double yb = cmsEvalToneCurveFloat(b, x);
129-
if (std::abs(ya - yb) > 0.001) // close enough
130-
return false;
131-
}
132-
133-
return true;
134-
}
135-
136126
static std::string dumpToneCurve(cmsToneCurve* c) {
137127
if (!c)
138128
return "";
@@ -152,6 +142,25 @@ static std::string dumpToneCurve(cmsToneCurve* c) {
152142
return res;
153143
}
154144

145+
static SP<CTexture> uploadRamp(std::span<const float> data) {
146+
g_pHyprRenderer->makeEGLCurrent();
147+
148+
SP<CTexture> tex = makeShared<CTexture>(data);
149+
150+
return tex;
151+
}
152+
153+
static std::vector<float> sampleToneCurve(cmsToneCurve* curve) {
154+
std::vector<float> lut;
155+
lut.reserve(LUT_SIZE);
156+
for (size_t i = 0; i < LUT_SIZE; ++i) {
157+
float x = sc<float>(i) / sc<float>(LUT_SIZE - 1);
158+
float y = sc<float>(cmsEvalToneCurveFloat(curve, x));
159+
lut.emplace_back(std::clamp(y, 0.F, 1.F));
160+
}
161+
return lut;
162+
}
163+
155164
std::expected<SImageDescription, std::string> SImageDescription::fromICC(const std::filesystem::path& file) {
156165
std::error_code ec;
157166
if (!std::filesystem::exists(file, ec) || ec)
@@ -228,37 +237,30 @@ std::expected<SImageDescription, std::string> SImageDescription::fromICC(const s
228237
Log::logger->log(Log::DEBUG, "TRC G: {}", dumpToneCurve(trcG));
229238
Log::logger->log(Log::DEBUG, "TRC B: {}", dumpToneCurve(trcB));
230239

231-
// FIXME: make this representable...
232-
cmsToneCurve* trc = trcR;
233-
if (trcR && trcG && trcB && (!toneCurvesSimilar(trcR, trcG) || !toneCurvesSimilar(trcR, trcB))) {
234-
Log::logger->log(Log::DEBUG, "============= End ICC load =============");
235-
return std::unexpected("Hyprland cannot represent split-trc profiles yet");
236-
}
240+
if (trcR && trcG && trcB) {
241+
Log::logger->log(Log::DEBUG, "All TRC present, uploading LUTs", dumpToneCurve(trcR));
237242

238-
if (trc) {
239-
if (cmsIsToneCurveLinear(trc)) {
240-
Log::logger->log(Log::DEBUG, "TRC: Linear");
241-
image.transferFunction = CM_TRANSFER_FUNCTION_EXT_LINEAR;
242-
image.transferFunctionPower = 1.F;
243-
} else {
244-
// gamma fit as a fallback
245-
Log::logger->log(Log::DEBUG, "TRC: Gamma fit");
246-
const double g = cmsEstimateGamma(trc, 0.000001);
247-
if (g > 1.0 && g < 4.0) {
248-
Log::logger->log(Log::DEBUG, "TRC: Gamma fit at {}", g);
249-
image.transferFunctionPower = (float)g;
250-
251-
if (std::abs(g - 2.2) < 0.05)
252-
image.transferFunction = CM_TRANSFER_FUNCTION_GAMMA22;
253-
else if (std::abs(g - 2.8) < 0.05)
254-
image.transferFunction = CM_TRANSFER_FUNCTION_GAMMA28;
255-
else
256-
image.transferFunction = CM_TRANSFER_FUNCTION_EXT_SRGB;
257-
} else {
258-
Log::logger->log(Log::DEBUG, "TRC: Gamma fit failed, falling back to srgb");
259-
image.transferFunction = CM_TRANSFER_FUNCTION_SRGB;
260-
}
261-
}
243+
image.lutRamps.present = true;
244+
245+
cmsToneCurve* invR = cmsReverseToneCurve(trcR);
246+
cmsToneCurve* invG = cmsReverseToneCurve(trcG);
247+
cmsToneCurve* invB = cmsReverseToneCurve(trcB);
248+
249+
CScopeGuard x2([&] {
250+
if (invR)
251+
cmsFreeToneCurve(invR);
252+
if (invG)
253+
cmsFreeToneCurve(invG);
254+
if (invB)
255+
cmsFreeToneCurve(invB);
256+
});
257+
258+
if (!invR || !invG || !invB)
259+
return std::unexpected("Failed to invert TRC, likely invalid/unsupported data");
260+
261+
image.lutRamps.r = uploadRamp(sampleToneCurve(invR));
262+
image.lutRamps.g = uploadRamp(sampleToneCurve(invG));
263+
image.lutRamps.b = uploadRamp(sampleToneCurve(invB));
262264
}
263265

264266
Log::logger->log(Log::DEBUG, "============= End ICC load =============");

src/render/OpenGL.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,10 @@ static void getCMShaderUniforms(SShader& shader) {
967967
shader.uniformLocations[SHADER_SDR_SATURATION] = glGetUniformLocation(shader.program, "sdrSaturation");
968968
shader.uniformLocations[SHADER_SDR_BRIGHTNESS] = glGetUniformLocation(shader.program, "sdrBrightnessMultiplier");
969969
shader.uniformLocations[SHADER_CONVERT_MATRIX] = glGetUniformLocation(shader.program, "convertMatrix");
970+
shader.uniformLocations[SHADER_USE_LUT] = glGetUniformLocation(shader.program, "useLUTRamps");
971+
shader.uniformLocations[SHADER_LUT_R] = glGetUniformLocation(shader.program, "dstLUTR");
972+
shader.uniformLocations[SHADER_LUT_G] = glGetUniformLocation(shader.program, "dstLUTG");
973+
shader.uniformLocations[SHADER_LUT_B] = glGetUniformLocation(shader.program, "dstLUTB");
970974
}
971975

972976
// shader has #include "rounding.glsl"
@@ -1614,6 +1618,26 @@ void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SI
16141618
primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix));
16151619
}
16161620
shader.setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]);
1621+
1622+
if (targetImageDescription.lutRamps.present) {
1623+
// this will override any dstTF set
1624+
shader.setUniformInt(SHADER_USE_LUT, 1);
1625+
1626+
// TODO: make this less hacky or sumn
1627+
glActiveTexture(GL_TEXTURE5);
1628+
targetImageDescription.lutRamps.r->bind();
1629+
1630+
glActiveTexture(GL_TEXTURE6);
1631+
targetImageDescription.lutRamps.g->bind();
1632+
1633+
glActiveTexture(GL_TEXTURE7);
1634+
targetImageDescription.lutRamps.b->bind();
1635+
1636+
shader.setUniformInt(SHADER_LUT_R, 5);
1637+
shader.setUniformInt(SHADER_LUT_G, 6);
1638+
shader.setUniformInt(SHADER_LUT_B, 7);
1639+
} else
1640+
shader.setUniformInt(SHADER_USE_LUT, 0);
16171641
}
16181642

16191643
void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const SImageDescription& imageDescription) {

src/render/Shader.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ enum eShaderUniform : uint8_t {
7676
SHADER_POINTER_INACTIVE_TIMEOUT,
7777
SHADER_POINTER_LAST_ACTIVE,
7878
SHADER_POINTER_SIZE,
79+
SHADER_USE_LUT,
80+
SHADER_LUT_R,
81+
SHADER_LUT_G,
82+
SHADER_LUT_B,
7983

8084
SHADER_LAST,
8185
};

src/render/Texture.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ CTexture::CTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy) : m_
5858
createFromDma(attrs, image);
5959
}
6060

61+
CTexture::CTexture(std::span<const float> data1D) : m_type(TEXTURE_R), m_size(data1D.size(), 1), m_isSynchronous(true) {
62+
allocate();
63+
bind();
64+
setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
65+
setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
66+
setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
67+
setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
68+
69+
GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, data1D.size(), 1, 0, GL_RED, GL_FLOAT, data1D.data()));
70+
unbind();
71+
}
72+
6173
void CTexture::createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_) {
6274
g_pHyprRenderer->makeEGLCurrent();
6375

src/render/Texture.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "../defines.hpp"
44
#include <aquamarine/buffer/Buffer.hpp>
55
#include <hyprutils/math/Misc.hpp>
6+
#include <span>
67

78
class IHLBuffer;
89
HYPRUTILS_FORWARD(Math, CRegion);
@@ -11,6 +12,7 @@ enum eTextureType : int8_t {
1112
TEXTURE_INVALID = -1, // Invalid
1213
TEXTURE_RGBA = 0, // 4 channels
1314
TEXTURE_RGBX, // discard A
15+
TEXTURE_R, // only 1 channel
1416
TEXTURE_EXTERNAL, // EGLImage
1517
};
1618

@@ -24,6 +26,7 @@ class CTexture {
2426
CTexture(const CTexture&) = delete;
2527

2628
CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false);
29+
CTexture(std::span<const float> data1D);
2730

2831
CTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy = false);
2932
// this ctor takes ownership of the eglImage.

src/render/shaders/glsl/CM.glsl

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ uniform float sdrSaturation;
99
uniform float sdrBrightnessMultiplier;
1010
uniform mat3 convertMatrix;
1111

12+
uniform int useLUTRamps;
13+
uniform sampler2D dstLUTR;
14+
uniform sampler2D dstLUTG;
15+
uniform sampler2D dstLUTB;
16+
17+
// IMPORTANT: this needs to match LUT_SIZE in ICC.cpp
18+
#define LUT_SIZE 2048.0
19+
1220
//enum eTransferFunction
1321
#define CM_TRANSFER_FUNCTION_BT1886 1
1422
#define CM_TRANSFER_FUNCTION_GAMMA22 2
@@ -68,6 +76,19 @@ uniform mat3 convertMatrix;
6876

6977
#define M_E 2.718281828459045
7078

79+
float sampleLUT(sampler2D t, float x) {
80+
float u = (clamp(x, 0.0, 1.0) * (LUT_SIZE - 1.0) + 0.5) / LUT_SIZE;
81+
return texture(t, vec2(u, 0.5)).r;
82+
}
83+
84+
vec3 linearToOutputWithLUT(vec3 lin) {
85+
return vec3(
86+
sampleLUT(dstLUTR, lin.r),
87+
sampleLUT(dstLUTG, lin.g),
88+
sampleLUT(dstLUTB, lin.b)
89+
);
90+
}
91+
7192
vec3 xy2xyz(vec2 xy) {
7293
if (xy.y == 0.0)
7394
return vec3(0.0, 0.0, 0.0);
@@ -268,13 +289,16 @@ vec4 fromLinear(vec4 color, int tf) {
268289
return color;
269290
}
270291

271-
vec4 fromLinearNit(vec4 color, int tf, vec2 range) {
272-
if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR)
292+
vec4 fromLinearNit(vec4 color, int tf, vec2 range) {
293+
if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR && useLUTRamps != 1)
273294
color.rgb = color.rgb / SDR_MAX_LUMINANCE;
274295
else {
275296
color.rgb /= max(color.a, 0.001);
276297
color.rgb = (color.rgb - range[0]) / (range[1] - range[0]);
277-
color.rgb = fromLinearRGB(color.rgb, tf);
298+
if (useLUTRamps == 1)
299+
color.rgb = linearToOutputWithLUT(color.rgb);
300+
else
301+
color.rgb = fromLinearRGB(color.rgb, tf);
278302
color.rgb *= color.a;
279303
}
280304
return color;

0 commit comments

Comments
 (0)