summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-03-04 19:50:28 +0000
committerGitHub <noreply@github.com>2026-03-04 19:50:28 +0000
commit10754745a97c99449ef40db58bd425f5a91f9993 (patch)
tree8aa9f57bcfb2157b9fbdc387c9021db6e6a38e46 /src/render
parent8271cfc97b76d573f9122032d6cf5db256d63470 (diff)
render/cm: add ICC profile pipeline (#12711)
Adds an ICC profile pipeline, loading via config and applying via 3D LUTs.
Diffstat (limited to 'src/render')
-rw-r--r--src/render/OpenGL.cpp361
-rw-r--r--src/render/OpenGL.hpp11
-rw-r--r--src/render/Shader.cpp25
-rw-r--r--src/render/Shader.hpp4
-rw-r--r--src/render/Texture.cpp26
-rw-r--r--src/render/Texture.hpp3
-rw-r--r--src/render/shaders/glsl/CM.glsl42
-rw-r--r--src/render/shaders/glsl/shadow.frag6
8 files changed, 289 insertions, 189 deletions
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index 916091db..b31a0e15 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -19,7 +19,7 @@
#include "../protocols/LayerShell.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/ColorManagement.hpp"
-#include "../protocols/types/ColorManagement.hpp"
+#include "../helpers/cm/ColorManagement.hpp"
#include "../managers/input/InputManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/CursorManager.hpp"
@@ -28,6 +28,7 @@
#include "../helpers/MainLoopExecutor.hpp"
#include "../i18n/Engine.hpp"
#include "../event/EventBus.hpp"
+#include "../managers/screenshare/ScreenshareManager.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "hyprerror/HyprError.hpp"
#include "pass/TexPassElement.hpp"
@@ -747,12 +748,24 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb
m_renderData.pCurrentMonData->mirrorSwapFB.addStencil(m_renderData.pCurrentMonData->stencilTex);
}
- if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty())
+ const bool HAS_MIRROR_FB = m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated();
+ const bool NEEDS_COPY_FB = needsACopyFB(m_renderData.pMonitor.lock());
+
+ if (HAS_MIRROR_FB && !NEEDS_COPY_FB)
m_renderData.pCurrentMonData->monitorMirrorFB.release();
+ else if (!HAS_MIRROR_FB && NEEDS_COPY_FB)
+ m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y,
+ m_renderData.pMonitor->m_output->state->state().drmFormat);
m_renderData.transformDamage = true;
- m_renderData.damage.set(damage_);
- m_renderData.finalDamage.set(finalDamage.value_or(damage_));
+ if (HAS_MIRROR_FB != NEEDS_COPY_FB) {
+ // force full damage because the mirror fb will be empty
+ m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX});
+ m_renderData.finalDamage.set(m_renderData.damage);
+ } else {
+ m_renderData.damage.set(damage_);
+ m_renderData.finalDamage.set(finalDamage.value_or(damage_));
+ }
m_fakeFrame = fb;
@@ -777,6 +790,12 @@ void CHyprOpenGLImpl::end() {
TRACY_GPU_ZONE("RenderEnd");
+ m_renderData.currentWindow.reset();
+ m_renderData.surface.reset();
+ m_renderData.currentLS.reset();
+ m_renderData.clipBox = {};
+ m_renderData.clipRegion.clear();
+
// end the render, copy the data to the main framebuffer
if LIKELY (m_offloadedFramebuffer) {
m_renderData.damage = m_renderData.finalDamage;
@@ -793,17 +812,20 @@ void CHyprOpenGLImpl::end() {
m_renderData.useNearestNeighbor = true;
// copy the damaged areas into the mirror buffer
- // we can't use the offloadFB for mirroring, as it contains artifacts from blurring
- if UNLIKELY (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame)
+ // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring
+ if UNLIKELY (needsACopyFB(m_renderData.pMonitor.lock()) && !m_fakeFrame)
saveBufferForMirror(monbox);
m_renderData.outFB->bind();
blend(false);
- if LIKELY (m_finalScreenShader->program() < 1 && !g_pHyprRenderer->m_crashingInProgress)
+ const auto PRIMITIVE_BLOCKED =
+ m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress || m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{};
+
+ if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL)
renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox);
- else
- renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {});
+ else // we need to use renderTexture if we do any CM whatsoever.
+ renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {.finalMonitorCM = true});
blend(true);
@@ -849,6 +871,10 @@ void CHyprOpenGLImpl::end() {
}
}
+bool CHyprOpenGLImpl::needsACopyFB(PHLMONITOR mon) {
+ return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon);
+}
+
void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional<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 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;
+
+ // 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;
- 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;
+ // otherwise, use chosen, we're drawing into the work buffer
+ // NOLINTNEXTLINE
+ return WORK_BUFFER_IMAGE_DESCRIPTION;
+ }();
- 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 */
+ 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);
-
- 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);
+ // 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);
- }
+ } 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));
}
}
diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp
index bc1f5f4d..d801d40b 100644
--- a/src/render/OpenGL.hpp
+++ b/src/render/OpenGL.hpp
@@ -142,7 +142,7 @@ struct SMonitorRenderData {
CFramebuffer mirrorFB; // these are used for some effects,
CFramebuffer mirrorSwapFB; // etc
CFramebuffer offMainFB;
- CFramebuffer monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB
+ CFramebuffer monitorMirrorFB; // used for mirroring outputs / screencopy, does not contain artifacts like offloadFB and is in sRGB
CFramebuffer blurFB;
SP<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);
diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp
index 5f62232c..5f18b906 100644
--- a/src/render/Shader.cpp
+++ b/src/render/Shader.cpp
@@ -127,7 +127,6 @@ void CShader::getUniformLocations() {
m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType");
// shader has #include "CM.glsl"
- m_uniformLocations[SHADER_SKIP_CM] = getUniform("skipCM");
m_uniformLocations[SHADER_SOURCE_TF] = getUniform("sourceTF");
m_uniformLocations[SHADER_TARGET_TF] = getUniform("targetTF");
m_uniformLocations[SHADER_SRC_TF_RANGE] = getUniform("srcTFRange");
@@ -140,6 +139,9 @@ void CShader::getUniformLocations() {
m_uniformLocations[SHADER_SDR_SATURATION] = getUniform("sdrSaturation");
m_uniformLocations[SHADER_SDR_BRIGHTNESS] = getUniform("sdrBrightnessMultiplier");
m_uniformLocations[SHADER_CONVERT_MATRIX] = getUniform("convertMatrix");
+ m_uniformLocations[SHADER_USE_ICC] = getUniform("useIcc");
+ m_uniformLocations[SHADER_LUT_3D] = getUniform("iccLut3D");
+ m_uniformLocations[SHADER_LUT_SIZE] = getUniform("iccLutSize");
//
m_uniformLocations[SHADER_TEX] = getUniform("tex");
m_uniformLocations[SHADER_ALPHA] = getUniform("alpha");
@@ -248,7 +250,8 @@ void CShader::setUniformInt(eShaderUniform location, GLint v0) {
return;
cached = v0;
- glUniform1i(m_uniformLocations[location], v0);
+
+ GLCALL(glUniform1i(m_uniformLocations[location], v0));
}
void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) {
@@ -264,7 +267,7 @@ void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) {
}
cached = v0;
- glUniform1f(m_uniformLocations[location], v0);
+ GLCALL(glUniform1f(m_uniformLocations[location], v0));
}
void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) {
@@ -280,7 +283,7 @@ void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1)
}
cached = std::array<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();
}
}
diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp
index 9f871c0e..184f6771 100644
--- a/src/render/Shader.hpp
+++ b/src/render/Shader.hpp
@@ -9,7 +9,6 @@ enum eShaderUniform : uint8_t {
SHADER_COLOR,
SHADER_ALPHA_MATTE,
SHADER_TEX_TYPE,
- SHADER_SKIP_CM,
SHADER_SOURCE_TF,
SHADER_TARGET_TF,
SHADER_SRC_TF_RANGE,
@@ -75,6 +74,9 @@ enum eShaderUniform : uint8_t {
SHADER_POINTER_INACTIVE_TIMEOUT,
SHADER_POINTER_LAST_ACTIVE,
SHADER_POINTER_SIZE,
+ SHADER_USE_ICC,
+ SHADER_LUT_3D,
+ SHADER_LUT_SIZE,
SHADER_LAST,
};
diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp
index 0e807485..1a35e488 100644
--- a/src/render/Texture.cpp
+++ b/src/render/Texture.cpp
@@ -58,6 +58,32 @@ CTexture::CTexture(const SP<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();
diff --git a/src/render/Texture.hpp b/src/render/Texture.hpp
index c2e9b2c3..a5806e26 100644
--- a/src/render/Texture.hpp
+++ b/src/render/Texture.hpp
@@ -3,6 +3,7 @@
#include "../defines.hpp"
#include <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.
diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl
index 36c95a90..66d84885 100644
--- a/src/render/shaders/glsl/CM.glsl
+++ b/src/render/shaders/glsl/CM.glsl
@@ -6,6 +6,10 @@ uniform mat3 convertMatrix;
#include "sdr_mod.glsl"
+uniform int useIcc;
+uniform highp sampler3D iccLut3D;
+uniform float iccLutSize;
+
//enum eTransferFunction
#define CM_TRANSFER_FUNCTION_BT1886 1
#define CM_TRANSFER_FUNCTION_GAMMA22 2
@@ -65,6 +69,24 @@ uniform mat3 convertMatrix;
#define M_E 2.718281828459045
+
+vec3 applyIcc3DLut(vec3 linearRgb01) {
+ vec3 x = clamp(linearRgb01, 0.0, 1.0);
+
+ // Map [0..1] to texel centers to avoid edge issues
+ float N = iccLutSize;
+ vec3 coord = (x * (N - 1.0) + 0.5) / N;
+
+ return texture(iccLut3D, coord).rgb;
+}
+
+vec3 xy2xyz(vec2 xy) {
+ if (xy.y == 0.0)
+ return vec3(0.0, 0.0, 0.0);
+
+ return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y);
+}
+
// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf
vec3 tfInvPQ(vec3 color) {
vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2));
@@ -250,7 +272,7 @@ vec4 fromLinear(vec4 color, int tf) {
return color;
}
-vec4 fromLinearNit(vec4 color, int tf, vec2 range) {
+vec4 fromLinearNit(vec4 color, int tf, vec2 range) {
if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR)
color.rgb = color.rgb / SDR_MAX_LUMINANCE;
else {
@@ -267,12 +289,18 @@ vec4 fromLinearNit(vec4 color, int tf, vec2 range) {
vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat3 dstxyz) {
pixColor.rgb /= max(pixColor.a, 0.001);
pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF);
- pixColor.rgb = convertMatrix * pixColor.rgb;
- pixColor = toNit(pixColor, srcTFRange);
- pixColor.rgb *= pixColor.a;
- #include "do_tonemap.glsl"
- pixColor = fromLinearNit(pixColor, dstTF, dstTFRange);
- #include "do_sdr_mod.glsl"
+
+ if (useIcc == 1) {
+ pixColor.rgb = applyIcc3DLut(pixColor.rgb);
+ pixColor.rgb *= pixColor.a;
+ } else {
+ pixColor.rgb = convertMatrix * pixColor.rgb;
+ pixColor = toNit(pixColor, srcTFRange);
+ pixColor.rgb *= pixColor.a;
+ #include "do_tonemap.glsl"
+ pixColor = fromLinearNit(pixColor, dstTF, dstTFRange);
+ #include "do_sdr_mod.glsl"
+ }
return pixColor;
}
diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag
index 71e96ddb..06aa605c 100644
--- a/src/render/shaders/glsl/shadow.frag
+++ b/src/render/shaders/glsl/shadow.frag
@@ -5,7 +5,6 @@ precision highp float;
in vec4 v_color;
in vec2 v_texcoord;
-uniform int skipCM;
uniform int sourceTF; // eTransferFunction
uniform int targetTF; // eTransferFunction
uniform mat3 targetPrimariesXYZ;
@@ -18,8 +17,6 @@ uniform float roundingPower;
uniform float range;
uniform float shadowPower;
-#include "CM.glsl"
-
float pixAlphaRoundedDistance(float distanceToCorner) {
if (distanceToCorner > radius) {
return 0.0;
@@ -92,8 +89,5 @@ void main() {
// premultiply
pixColor.rgb *= pixColor[3];
- if (skipCM == 0)
- pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimariesXYZ);
-
fragColor = pixColor;
} \ No newline at end of file