summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-03-27 10:08:56 -0400
committerGitHub <noreply@github.com>2026-03-27 14:08:56 +0000
commit2c4852e31f1cc974164fdd1bf383a67c26cafe44 (patch)
treec248ef256b6ff1d8de9fe031ff2bf33b67e85dde /src/render
parent2fde538e0fa5663ac99f631fcfee6742e09368f6 (diff)
renderer/deco: add glow decoration (#13862)
Diffstat (limited to 'src/render')
-rw-r--r--src/render/GLRenderer.cpp6
-rw-r--r--src/render/GLRenderer.hpp1
-rw-r--r--src/render/OpenGL.cpp71
-rw-r--r--src/render/OpenGL.hpp1
-rw-r--r--src/render/Renderer.cpp1
-rw-r--r--src/render/Renderer.hpp18
-rw-r--r--src/render/ShaderLoader.hpp1
-rw-r--r--src/render/decorations/CHyprInnerGlowDecoration.cpp104
-rw-r--r--src/render/decorations/CHyprInnerGlowDecoration.hpp35
-rw-r--r--src/render/decorations/IHyprWindowDecoration.hpp1
-rw-r--r--src/render/pass/InnerGlowPassElement.cpp13
-rw-r--r--src/render/pass/InnerGlowPassElement.hpp28
-rw-r--r--src/render/pass/PassElement.hpp3
-rw-r--r--src/render/shaders/glsl/inner_glow.frag57
-rw-r--r--src/render/shaders/glsl/inner_glow.glsl106
15 files changed, 433 insertions, 13 deletions
diff --git a/src/render/GLRenderer.cpp b/src/render/GLRenderer.cpp
index 48825a4b..afef2ba0 100644
--- a/src/render/GLRenderer.cpp
+++ b/src/render/GLRenderer.cpp
@@ -1,4 +1,5 @@
#include "GLRenderer.hpp"
+#include "decorations/CHyprInnerGlowDecoration.hpp"
#include <aquamarine/output/Output.hpp>
#include "../config/ConfigValue.hpp"
#include "../managers/CursorManager.hpp"
@@ -354,6 +355,11 @@ void CHyprGLRenderer::draw(CShadowPassElement* element, const CRegion& damage) {
m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a);
};
+void CHyprGLRenderer::draw(CInnerGlowPassElement* element, const CRegion& damage) {
+ const auto m_data = element->m_data;
+ m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a);
+};
+
void CHyprGLRenderer::draw(CTexPassElement* element, const CRegion& damage) {
const auto m_data = element->m_data;
diff --git a/src/render/GLRenderer.hpp b/src/render/GLRenderer.hpp
index 1446e9b2..6f511fc3 100644
--- a/src/render/GLRenderer.hpp
+++ b/src/render/GLRenderer.hpp
@@ -41,6 +41,7 @@ class CHyprGLRenderer : public IHyprRenderer {
void draw(CPreBlurElement* element, const CRegion& damage) override;
void draw(CRectPassElement* element, const CRegion& damage) override;
void draw(CShadowPassElement* element, const CRegion& damage) override;
+ void draw(CInnerGlowPassElement* element, const CRegion& damage) override;
void draw(CTexPassElement* element, const CRegion& damage) override;
void draw(CTextureMatteElement* element, const CRegion& damage) override;
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index 16e892ca..9aaa99d5 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -853,14 +853,14 @@ void CHyprOpenGLImpl::end() {
}
static const std::vector<std::string> SHADER_INCLUDES = {
- "defines.h", "constants.h", "cm_helpers.glsl", "rounding.glsl", "CM.glsl", "tonemap.glsl", "gain.glsl",
- "border.glsl", "shadow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl",
+ "defines.h", "constants.h", "cm_helpers.glsl", "rounding.glsl", "CM.glsl", "tonemap.glsl", "gain.glsl",
+ "border.glsl", "shadow.glsl", "inner_glow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl",
};
// order matters, see ePreparedFragmentShader
const std::array<std::string, SH_FRAG_LAST> FRAG_SHADERS = {
- "quad.frag", "passthru.frag", "rgbamatte.frag", "ext.frag", "blur1.frag", "blur2.frag",
- "blurprepare.frag", "blurfinish.frag", "shadow.frag", "surface.frag", "border.frag", "glitch.frag",
+ "quad.frag", "passthru.frag", "rgbamatte.frag", "ext.frag", "blur1.frag", "blur2.frag", "blurprepare.frag",
+ "blurfinish.frag", "shadow.frag", "inner_glow.frag", "surface.frag", "border.frag", "glitch.frag",
};
bool CHyprOpenGLImpl::initShaders(const std::string& path) {
@@ -2283,6 +2283,69 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun
glBindVertexArray(0);
}
+void CHyprOpenGLImpl::renderInnerGlow(const CBox& box, int round, float roundingPower, int range, const CHyprColor& color, int glowPower, float a) {
+ auto& m_renderData = g_pHyprRenderer->m_renderData;
+ RASSERT(m_renderData.pMonitor, "Tried to render inner glow without begin()!");
+ RASSERT((box.width > 0 && box.height > 0), "Tried to render inner glow with width/height < 0!");
+
+ if (g_pHyprRenderer->m_renderData.damage.empty())
+ return;
+
+ TRACY_GPU_ZONE("RenderInnerGlow");
+
+ CBox newBox = box;
+ g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox);
+
+ const auto col = color;
+
+ const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox);
+
+ blend(true);
+
+ const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present;
+ const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id();
+ auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD)));
+ if (!skipCM)
+ passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION);
+
+ shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
+ shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a);
+
+ const auto TOPLEFT = Vector2D(round, round);
+ const auto BOTTOMRIGHT = Vector2D(newBox.width - round, newBox.height - round);
+ const auto FULLSIZE = Vector2D(newBox.width, newBox.height);
+
+ shader->setUniformFloat2(SHADER_TOP_LEFT, sc<float>(TOPLEFT.x), sc<float>(TOPLEFT.y));
+ shader->setUniformFloat2(SHADER_BOTTOM_RIGHT, sc<float>(BOTTOMRIGHT.x), sc<float>(BOTTOMRIGHT.y));
+ shader->setUniformFloat2(SHADER_FULL_SIZE, sc<float>(FULLSIZE.x), sc<float>(FULLSIZE.y));
+ shader->setUniformFloat(SHADER_RADIUS, round);
+ shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower);
+ shader->setUniformFloat(SHADER_RANGE, range);
+ shader->setUniformFloat(SHADER_SHADOW_POWER, glowPower);
+
+ glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
+
+ if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) {
+ CRegion damageClip{g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width,
+ g_pHyprRenderer->m_renderData.clipBox.height};
+ damageClip.intersect(g_pHyprRenderer->m_renderData.damage);
+
+ if (!damageClip.empty()) {
+ damageClip.forEachRect([this](const auto& RECT) {
+ scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ });
+ }
+ } else {
+ g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) {
+ scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ });
+ }
+
+ glBindVertexArray(0);
+}
+
void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) {
auto& m_renderData = g_pHyprRenderer->m_renderData;
if (!g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->isAllocated())
diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp
index e92d4822..bbca01c0 100644
--- a/src/render/OpenGL.hpp
+++ b/src/render/OpenGL.hpp
@@ -228,6 +228,7 @@ class CHyprOpenGLImpl {
void renderRect(const CBox&, const CHyprColor&, SRectRenderData data);
void renderTexture(SP<ITexture>, const CBox&, STextureRenderData data);
void renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0);
+ void renderInnerGlow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, int glowPower, float a = 1.0);
void renderBorder(const CBox&, const Config::CGradientValueData&, SBorderRenderData data);
void renderBorder(const CBox&, const Config::CGradientValueData&, const Config::CGradientValueData&, float lerp, SBorderRenderData data);
void renderTextureMatte(SP<ITexture> tex, const CBox& pBox, SP<IFramebuffer> matte);
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index 66d48247..0c184238 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -1105,6 +1105,7 @@ void IHyprRenderer::draw(WP<IPassElement> element, const CRegion& damage) {
case EK_RECT: drawRect(dc<CRectPassElement*>(element.get()), damage); break;
case EK_HINTS: drawHints(dc<CRendererHintsPassElement*>(element.get()), damage); break;
case EK_SHADOW: draw(dc<CShadowPassElement*>(element.get()), damage); break;
+ case EK_INNER_GLOW: draw(dc<CInnerGlowPassElement*>(element.get()), damage); break;
case EK_SURFACE: preDrawSurface(dc<CSurfacePassElement*>(element.get()), damage); break;
case EK_TEXTURE: drawTex(dc<CTexPassElement*>(element.get()), damage); break;
case EK_TEXTURE_MATTE: drawTexMatte(dc<CTextureMatteElement*>(element.get()), damage); break;
diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp
index 4b65141a..d8056d82 100644
--- a/src/render/Renderer.hpp
+++ b/src/render/Renderer.hpp
@@ -23,6 +23,7 @@
#include "pass/RectPassElement.hpp"
#include "pass/RendererHintsPassElement.hpp"
#include "pass/ShadowPassElement.hpp"
+#include "pass/InnerGlowPassElement.hpp"
#include "pass/SurfacePassElement.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/TextureMatteElement.hpp"
@@ -288,14 +289,15 @@ class IHyprRenderer {
};
SP<ITexture> getBackground(PHLMONITOR pMonitor);
- virtual void draw(CBorderPassElement* element, const CRegion& damage) = 0;
- virtual void draw(CClearPassElement* element, const CRegion& damage) = 0;
- virtual void draw(CFramebufferElement* element, const CRegion& damage) = 0;
- virtual void draw(CPreBlurElement* element, const CRegion& damage) = 0;
- virtual void draw(CRectPassElement* element, const CRegion& damage) = 0;
- virtual void draw(CShadowPassElement* element, const CRegion& damage) = 0;
- virtual void draw(CTexPassElement* element, const CRegion& damage) = 0;
- virtual void draw(CTextureMatteElement* element, const CRegion& damage) = 0;
+ virtual void draw(CBorderPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CClearPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CFramebufferElement* element, const CRegion& damage) = 0;
+ virtual void draw(CPreBlurElement* element, const CRegion& damage) = 0;
+ virtual void draw(CRectPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CShadowPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CInnerGlowPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CTexPassElement* element, const CRegion& damage) = 0;
+ virtual void draw(CTextureMatteElement* element, const CRegion& damage) = 0;
virtual SP<ITexture> getBlurTexture(PHLMONITORREF pMonitor);
SP<ITexture> m_lockDeadTexture;
SP<ITexture> m_lockDead2Texture;
diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp
index e522e9fa..9f3f787d 100644
--- a/src/render/ShaderLoader.hpp
+++ b/src/render/ShaderLoader.hpp
@@ -36,6 +36,7 @@ namespace Render {
SH_FRAG_BLURPREPARE,
SH_FRAG_BLURFINISH,
SH_FRAG_SHADOW,
+ SH_FRAG_INNER_GLOW,
SH_FRAG_SURFACE,
SH_FRAG_BORDER1,
SH_FRAG_GLITCH,
diff --git a/src/render/decorations/CHyprInnerGlowDecoration.cpp b/src/render/decorations/CHyprInnerGlowDecoration.cpp
new file mode 100644
index 00000000..a60deccb
--- /dev/null
+++ b/src/render/decorations/CHyprInnerGlowDecoration.cpp
@@ -0,0 +1,104 @@
+#include "CHyprInnerGlowDecoration.hpp"
+
+#include "../../Compositor.hpp"
+#include "../pass/InnerGlowPassElement.hpp"
+#include "../Renderer.hpp"
+#include "../OpenGL.hpp"
+
+CHyprInnerGlowDecoration::CHyprInnerGlowDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) {
+ ;
+}
+
+eDecorationType CHyprInnerGlowDecoration::getDecorationType() {
+ return DECORATION_INNER_GLOW;
+}
+
+SDecorationPositioningInfo CHyprInnerGlowDecoration::getPositioningInfo() {
+ SDecorationPositioningInfo info;
+ info.policy = DECORATION_POSITION_ABSOLUTE;
+ info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP;
+ return info;
+}
+
+void CHyprInnerGlowDecoration::onPositioningReply(const SDecorationPositioningReply& reply) {
+ updateWindow(m_window.lock());
+}
+
+uint64_t CHyprInnerGlowDecoration::getDecorationFlags() {
+ return DECORATION_NON_SOLID;
+}
+
+std::string CHyprInnerGlowDecoration::getDisplayName() {
+ return "Inner Glow";
+}
+
+void CHyprInnerGlowDecoration::damageEntire() {
+ const auto PWINDOW = m_window.lock();
+ if (!validMapped(PWINDOW))
+ return;
+
+ CBox windowBox = PWINDOW->getWindowMainSurfaceBox();
+
+ const auto PWORKSPACE = PWINDOW->m_workspace;
+ if (PWORKSPACE && PWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOW->m_pinned)
+ windowBox.translate(PWORKSPACE->m_renderOffset->value());
+ windowBox.translate(PWINDOW->m_floatingOffset);
+
+ g_pHyprRenderer->damageRegion(CRegion(windowBox));
+}
+
+void CHyprInnerGlowDecoration::updateWindow(PHLWINDOW pWindow) {
+ const auto PWINDOW = m_window.lock();
+ m_lastWindowPos = PWINDOW->m_realPosition->value();
+ m_lastWindowSize = PWINDOW->m_realSize->value();
+}
+
+void CHyprInnerGlowDecoration::draw(PHLMONITOR pMonitor, float const& a) {
+ CInnerGlowPassElement::SInnerGlowData data;
+ data.deco = this;
+ data.a = a;
+ g_pHyprRenderer->m_renderPass.add(makeUnique<CInnerGlowPassElement>(data));
+}
+
+void CHyprInnerGlowDecoration::render(PHLMONITOR pMonitor, float const& a) {
+
+ static auto PGLOW = CConfigValue<Hyprlang::INT>("decoration:glow:enabled");
+ static auto PGLOWRANGE = CConfigValue<Hyprlang::INT>("decoration:glow:range");
+ static auto PGLOWPOWER = CConfigValue<Hyprlang::INT>("decoration:glow:render_power");
+
+ if (!*PGLOW)
+ return;
+
+ const auto PWINDOW = m_window.lock();
+
+ if (!validMapped(PWINDOW))
+ return;
+
+ const auto ROUNDING = PWINDOW->rounding() > 0 ? PWINDOW->rounding() - 1 : PWINDOW->rounding();
+ const auto ROUNDINGPOWER = PWINDOW->roundingPower();
+ const auto PWORKSPACE = PWINDOW->m_workspace;
+ const auto WORKSPACEOFF = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D();
+
+ CBox windowBox = {m_lastWindowPos.x, m_lastWindowPos.y, m_lastWindowSize.x, m_lastWindowSize.y};
+ windowBox.translate(-pMonitor->m_position + WORKSPACEOFF + PWINDOW->m_floatingOffset);
+ windowBox.scale(pMonitor->m_scale).round();
+
+ if (windowBox.width < 1 || windowBox.height < 1)
+ return;
+
+ const int GLOWSIZE = *PGLOWRANGE;
+ const float GLOWPOWER = *PGLOWPOWER;
+ const auto GLOWCOLOR = m_window->m_realGlowColor->value();
+
+ g_pHyprRenderer->m_renderData.currentWindow = m_window;
+
+ g_pHyprRenderer->blend(true);
+
+ g_pHyprOpenGL->renderInnerGlow(windowBox, ROUNDING * pMonitor->m_scale, ROUNDINGPOWER, GLOWSIZE * pMonitor->m_scale, GLOWCOLOR, GLOWPOWER, a);
+
+ g_pHyprRenderer->m_renderData.currentWindow.reset();
+}
+
+eDecorationLayer CHyprInnerGlowDecoration::getDecorationLayer() {
+ return DECORATION_LAYER_OVER;
+}
diff --git a/src/render/decorations/CHyprInnerGlowDecoration.hpp b/src/render/decorations/CHyprInnerGlowDecoration.hpp
new file mode 100644
index 00000000..a2e4d79a
--- /dev/null
+++ b/src/render/decorations/CHyprInnerGlowDecoration.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "IHyprWindowDecoration.hpp"
+
+class CHyprInnerGlowDecoration : public IHyprWindowDecoration {
+ public:
+ CHyprInnerGlowDecoration(PHLWINDOW);
+ virtual ~CHyprInnerGlowDecoration() = default;
+
+ virtual SDecorationPositioningInfo getPositioningInfo();
+
+ virtual void onPositioningReply(const SDecorationPositioningReply& reply);
+
+ virtual void draw(PHLMONITOR, float const& a);
+
+ virtual eDecorationType getDecorationType();
+
+ virtual void updateWindow(PHLWINDOW);
+
+ virtual void damageEntire();
+
+ virtual eDecorationLayer getDecorationLayer();
+
+ virtual uint64_t getDecorationFlags();
+
+ virtual std::string getDisplayName();
+
+ void render(PHLMONITOR, float const& a);
+
+ private:
+ PHLWINDOWREF m_window;
+
+ Vector2D m_lastWindowPos;
+ Vector2D m_lastWindowSize;
+};
diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp
index 9916d392..5f77ea42 100644
--- a/src/render/decorations/IHyprWindowDecoration.hpp
+++ b/src/render/decorations/IHyprWindowDecoration.hpp
@@ -9,6 +9,7 @@ enum eDecorationType : int8_t {
DECORATION_NONE = -1,
DECORATION_GROUPBAR,
DECORATION_SHADOW,
+ DECORATION_INNER_GLOW,
DECORATION_BORDER,
DECORATION_CUSTOM
};
diff --git a/src/render/pass/InnerGlowPassElement.cpp b/src/render/pass/InnerGlowPassElement.cpp
new file mode 100644
index 00000000..04ccebd7
--- /dev/null
+++ b/src/render/pass/InnerGlowPassElement.cpp
@@ -0,0 +1,13 @@
+#include "InnerGlowPassElement.hpp"
+
+CInnerGlowPassElement::CInnerGlowPassElement(const CInnerGlowPassElement::SInnerGlowData& data_) : m_data(data_) {
+ ;
+}
+
+bool CInnerGlowPassElement::needsLiveBlur() {
+ return false;
+}
+
+bool CInnerGlowPassElement::needsPrecomputeBlur() {
+ return false;
+}
diff --git a/src/render/pass/InnerGlowPassElement.hpp b/src/render/pass/InnerGlowPassElement.hpp
new file mode 100644
index 00000000..62db7791
--- /dev/null
+++ b/src/render/pass/InnerGlowPassElement.hpp
@@ -0,0 +1,28 @@
+#pragma once
+#include "PassElement.hpp"
+
+class CHyprInnerGlowDecoration;
+
+class CInnerGlowPassElement : public IPassElement {
+ public:
+ struct SInnerGlowData {
+ CHyprInnerGlowDecoration* deco = nullptr;
+ float a = 1.F;
+ };
+
+ CInnerGlowPassElement(const SInnerGlowData& data_);
+ virtual ~CInnerGlowPassElement() = default;
+
+ virtual bool needsLiveBlur();
+ virtual bool needsPrecomputeBlur();
+
+ virtual const char* passName() {
+ return "CInnerGlowPassElement";
+ }
+
+ virtual ePassElementType type() {
+ return EK_INNER_GLOW;
+ };
+
+ SInnerGlowData m_data;
+};
diff --git a/src/render/pass/PassElement.hpp b/src/render/pass/PassElement.hpp
index 0dc5f581..c262b36c 100644
--- a/src/render/pass/PassElement.hpp
+++ b/src/render/pass/PassElement.hpp
@@ -13,7 +13,8 @@ enum ePassElementType : uint8_t {
EK_SHADOW,
EK_SURFACE,
EK_TEXTURE,
- EK_TEXTURE_MATTE
+ EK_TEXTURE_MATTE,
+ EK_INNER_GLOW
};
class IPassElement {
diff --git a/src/render/shaders/glsl/inner_glow.frag b/src/render/shaders/glsl/inner_glow.frag
new file mode 100644
index 00000000..2fc405c0
--- /dev/null
+++ b/src/render/shaders/glsl/inner_glow.frag
@@ -0,0 +1,57 @@
+#version 300 es
+#define ALLOW_INCLUDES
+#extension GL_ARB_shading_language_include : enable
+
+#include "defines.h"
+
+precision highp float;
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform int sourceTF; // eTransferFunction
+uniform int targetTF; // eTransferFunction
+uniform mat3 targetPrimariesXYZ;
+
+uniform vec2 topLeft;
+uniform vec2 bottomRight;
+uniform vec2 fullSize;
+uniform float radius;
+uniform float roundingPower;
+uniform float range;
+uniform float shadowPower;
+
+#if USE_CM
+#include "cm_helpers.glsl"
+#include "CM.glsl"
+#endif
+
+#include "inner_glow.glsl"
+
+layout(location = 0) out vec4 fragColor;
+void main() {
+ vec4 pixColor = v_color;
+
+ fragColor = getInnerGlow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight
+#if USE_CM
+ ,
+ sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
+#if USE_ICC
+ ,
+ iccLut3D, iccLutSize
+#else
+#if USE_TONEMAP || USE_SDR_MOD
+ ,
+ targetPrimariesXYZ
+#endif
+#if USE_TONEMAP
+ ,
+ maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance
+#endif
+#if USE_SDR_MOD
+ ,
+ sdrSaturation, sdrBrightnessMultiplier
+#endif
+#endif
+#endif
+ );
+}
diff --git a/src/render/shaders/glsl/inner_glow.glsl b/src/render/shaders/glsl/inner_glow.glsl
new file mode 100644
index 00000000..b0d55b19
--- /dev/null
+++ b/src/render/shaders/glsl/inner_glow.glsl
@@ -0,0 +1,106 @@
+#ifndef ALLOW_INCLUDES
+#define ALLOW_INCLUDES
+#extension GL_ARB_shading_language_include : enable
+#endif
+#ifndef INNER_GLOW_GLSL
+#define INNER_GLOW_GLSL
+
+#include "cm_helpers.glsl"
+
+float innerGlowAlpha(float distFromEdge, float range, float glowPower) {
+ if (distFromEdge >= range)
+ return 0.0;
+
+ if (distFromEdge <= 0.0)
+ return 1.0;
+
+ return pow(1.0 - distFromEdge / range, glowPower);
+}
+
+float innerGlowModifiedLength(vec2 a, float roundingPower) {
+ return pow(pow(abs(a.x), roundingPower) + pow(abs(a.y), roundingPower), 1.0 / roundingPower);
+}
+
+float innerGlowSmin(float a, float b, float k) {
+ float h = max(k - abs(a - b), 0.0) / k;
+ return min(a, b) - h * h * h * k * (1.0 / 6.0);
+}
+
+vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, vec2 bottomRight
+#if USE_CM
+ ,
+ int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange
+#if USE_ICC
+ ,
+ highp sampler3D iccLut3D, float iccLutSize
+#else
+#if USE_TONEMAP || USE_SDR_MOD
+ ,
+ mat3 targetPrimariesXYZ
+#endif
+#if USE_TONEMAP
+ ,
+ float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance
+#endif
+#if USE_SDR_MOD
+ ,
+ float sdrSaturation, float sdrBrightnessMultiplier
+#endif
+#endif
+#endif
+) {
+ vec2 pixCoord = fullSize * v_texcoord;
+
+ // clip to the rounded rectangle shape using actual SDF
+ vec2 center = fullSize * 0.5;
+ vec2 p = abs(pixCoord - center);
+ vec2 q = p - (center - vec2(radius));
+ vec2 qc = max(q, vec2(0.0));
+ float cornerD = (qc.x > 0.0 || qc.y > 0.0) ? innerGlowModifiedLength(qc, roundingPower) : 0.0;
+ float sdfDist = cornerD + min(max(q.x, q.y), 0.0) - radius;
+
+ if (sdfDist > 0.0)
+ discard;
+
+ // smooth-min of edge distances for rounded glow contours
+ float distT = pixCoord.y;
+ float distB = fullSize.y - pixCoord.y;
+ float distL = pixCoord.x;
+ float distR = fullSize.x - pixCoord.x;
+
+ float k = range;
+ float distFromEdge = innerGlowSmin(innerGlowSmin(distT, distB, k), innerGlowSmin(distL, distR, k), k);
+
+ pixColor[3] = pixColor[3] * innerGlowAlpha(distFromEdge, range, glowPower);
+
+ if (pixColor[3] == 0.0)
+ discard;
+
+ // premultiply
+ pixColor.rgb *= pixColor[3];
+
+#if USE_CM
+ pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
+#if USE_ICC
+ ,
+ iccLut3D, iccLutSize
+#else
+#if USE_TONEMAP || USE_SDR_MOD
+ ,
+ targetPrimariesXYZ
+#endif
+#if USE_TONEMAP
+ ,
+ maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance
+#endif
+#if USE_SDR_MOD
+ ,
+ sdrSaturation, sdrBrightnessMultiplier
+#endif
+#endif
+ );
+#endif
+
+ return pixColor;
+}
+#endif