renderer/gl: implement swappable blur variants (#15661)

Adds multiple variants to the blur shader
This commit is contained in:
Vaxry
2026-08-22 17:27:16 +02:00
committed by GitHub
parent 175aa0743a
commit d8e657eaea
86 changed files with 6201 additions and 527 deletions
@@ -76,6 +76,8 @@ void CPropRefresher::refreshProp(const bool execdAsScheduled) {
}
if (m_propsTripped & REFRESH_BLUR_FB) {
g_pHyprRenderer->refreshBlurProvider();
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
+75 -1
View File
@@ -235,7 +235,26 @@ std::vector<SP<IValue>> Values::getConfigValues() {
* blur:
*/
MS<Bool>("decoration:blur:enabled", "enable kawase window background blur", true, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Bool>("decoration:blur:enabled", "enable window background blur", true, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Int>("decoration:blur:variant", "set the blur variant. Blur variants enhance regular blur, but may increase GPU and CPU usage, significantly so if they are animated.",
0,
{.min = 0,
.max = 10,
.map =
OptionMap{
{"kawase", 0},
{"frost", 1},
{"ripple", 2},
{"drops", 3},
{"water", 4},
{"fluid_jar", 5},
{"prism", 6},
{"heat_shimmer", 7},
{"acrylic", 8},
{"aurora", 9},
{"haze", 10},
},
.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Int>("decoration:blur:size", "blur size (distance)", 8, {.min = 0, .max = 100, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Int>("decoration:blur:passes", "the amount of passes to perform", 1, {.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Bool>("decoration:blur:ignore_opacity", "make the blur layer ignore the opacity of the window", true, {.refresh = Supplementary::REFRESH_BLUR_FB}),
@@ -254,6 +273,61 @@ std::vector<SP<IValue>> Values::getConfigValues() {
MS<Float>("decoration:blur:input_methods_ignorealpha", "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur.", 0.2,
{.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
// specific blur stuff
MS<Float>("decoration:blur:glass:refraction", "maximum refraction displacement for glass blur types in pixels", 20.F,
{.min = 0, .max = 20, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:glass:size", "pattern size for glass blur types in pixels", 40.F, {.min = 4, .max = 512, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:glass:roughness", "strength of the glass relief shading", 1.F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:acrylic:refraction", "maximum acrylic lens displacement in pixels", 24.F, {.min = 0, .max = 48, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:acrylic:bulb", "width of the curved acrylic edge in pixels", 48.F, {.min = 4, .max = 256, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:acrylic:clarity", "amount of sharp backdrop transmitted through the acrylic surface", 0.82F,
{.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:acrylic:aberration", "relative chromatic separation in the acrylic lens", 0.025F,
{.min = 0, .max = 0.25, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Color>("decoration:blur:acrylic:tint", "acrylic tint color. Alpha controls optical absorption.", 0x14EEF5FF, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:drops:speed", "animation speed for drops blur. 0 disables the animation. Enabling will significantly increase GPU usage.", 3.F,
{.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:heat_shimmer:speed", "animation speed for heat shimmer blur. 0 disables the animation. Enabling will increase GPU usage.", 1.F,
{.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:aurora:speed", "animation speed for aurora blur. 0 freezes the animation. Enabling will increase GPU usage.", 1.F,
{.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:aurora:intensity", "strength of the aurora color contribution", 0.35F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Color>("decoration:blur:aurora:color1", "first aurora curtain color. Alpha controls its contribution.", 0x29F0A0FF, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Color>("decoration:blur:aurora:color2", "second aurora curtain color. Alpha controls its contribution.", 0x7A4DFFFF, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:haze:intensity", "strength of the haze pearlescent sheen", 0.35F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:haze:iridescence", "strength of the haze pearlescent color shift", 0.7F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:ripple:strength", "maximum refraction displacement of click ripples in pixels", 30.F,
{.min = 0, .max = 32, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:ripple:radius", "maximum radius of click ripples in pixels", 400.F, {.min = 1, .max = 1000, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:ripple:width", "width of click ripple waves in pixels", 32.F, {.min = 1, .max = 200, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:ripple:duration", "duration of click ripples in seconds", 0.45F, {.min = 0.05, .max = 5, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:water:strength", "maximum refraction displacement and injection strength for water blur in pixels", 32.F,
{.min = 0, .max = 32, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:water:radius", "pointer radius for water blur in pixels", 20.F, {.min = 1, .max = 1000, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:water:speed", "propagation speed for water blur", 0.76F, {.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:water:damping", "decay damping for water blur", 0.95F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:water:duration", "maximum water blur animation duration in seconds", 12.F, {.min = 0.5, .max = 60, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Color>("decoration:blur:fluid_jar:color", "fluid color for fluid jar blur", 0xCC3399FF, {.refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:speed", "animation speed for fluid jar blur", 3.7F, {.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:fill_amount", "fill amount for fluid jar blur", 0.5F, {.min = 0, .max = 1, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:mass", "inertial mass for fluid jar blur", 1.4F, {.min = 0.1, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:precision", "fluid simulation precision multiplier. 2x is a good compromise. 4x is expensive. 8x is extreme and unnecessary.", 2.F,
{.min = 0.5, .max = 8, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:turbulence", "interior fluid turbulence multiplier", 1.2F, {.min = 0, .max = 5, .refresh = Supplementary::REFRESH_BLUR_FB}),
MS<Float>("decoration:blur:fluid_jar:distortion", "fluid refraction distortion multiplier", 8.F, {.min = 0, .max = 10, .refresh = Supplementary::REFRESH_BLUR_FB}),
/*
* motion_blur:
*/
MS<Bool>("decoration:motion_blur:enabled", "enable motion blur for moving and resizing windows", false, {.refresh = Supplementary::REFRESH_WINDOW_STATES}),
MS<Int>("decoration:motion_blur:samples", "amount of samples used for motion blur", 7, {.min = 1, .max = 64, .refresh = Supplementary::REFRESH_WINDOW_STATES}),
MS<Bool>("decoration:wobble:enabled", "enable wobble deformation for moving and resizing windows", false, {.refresh = Supplementary::REFRESH_WINDOW_STATES}),
+13
View File
@@ -10,6 +10,7 @@
#include "../../animation/AnimationManager.hpp"
#include "../../render/Renderer.hpp"
#include "../../config/shared/animation/AnimationTree.hpp"
#include "../../config/ConfigValue.hpp"
#include "../../output/Monitor.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../ipc/s2/S2.hpp"
@@ -484,3 +485,15 @@ std::optional<uint8_t> CLayerSurface::alphaGenericToKey(eAlphaModifiableProp p)
static_assert(ALPHA_MODIFIABLE_LAST == 1);
UNREACHABLE();
}
bool CLayerSurface::shouldBlur() const {
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLUR)
return false;
auto surface = wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return m_ruleApplicator->blur().valueOrDefault();
}
+1
View File
@@ -54,6 +54,7 @@ namespace Desktop::View {
virtual std::optional<uint8_t> alphaGenericToKey(eAlphaModifiableProp p) override;
WP<CLayerShellResource> m_layerSurface;
bool shouldBlur() const;
LayerFlags m_flags = LAYER_FLAG_ABOVE_FULLSCREEN;
+7
View File
@@ -726,3 +726,10 @@ std::optional<uint8_t> CPopup::alphaGenericToKey(eAlphaModifiableProp p) {
static_assert(ALPHA_MODIFIABLE_LAST == 1);
UNREACHABLE();
}
bool CPopup::shouldBlur() const {
static CConfigValue PBLURPOPUPS = CConfigValue<Config::INTEGER>("decoration:blur:popups");
static CConfigValue PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
return *PBLURPOPUPS && *PBLUR;
}
+1
View File
@@ -68,6 +68,7 @@ namespace Desktop::View {
void onReposition();
void recheckTree();
bool shouldBlur() const;
bool inert() const;
+17 -1
View File
@@ -606,6 +606,22 @@ bool CWindow::isHidden() const {
return m_hidden;
}
bool CWindow::shouldBlur() const {
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLUR)
return false;
const bool DONT_BLUR = m_ruleApplicator->noBlur().valueOrDefault() || m_ruleApplicator->RGBX().valueOrDefault() || presentation().opaque();
if (DONT_BLUR)
return false;
auto surface = wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return true;
}
void CWindow::onInputBlockStateUpdated(bool blocked) {
if (blocked && Desktop::focusState()->window() == m_self)
Desktop::focusState()->fullWindowFocus(nullptr, eFocusReason::FOCUS_REASON_SWITCH_TO_WINDOW_SOFT);
@@ -1049,7 +1065,7 @@ bool CWindow::priorityFocus() {
return !m_backend->isX11() && CAsyncDialogBox::isPriorityDialogBox(m_backend->pid());
}
SP<CWLSurfaceResource> CWindow::getSolitaryResource() {
SP<CWLSurfaceResource> CWindow::getSolitaryResource() const {
if (!m_wlSurface || !m_wlSurface->resource())
return nullptr;
+2 -1
View File
@@ -199,6 +199,7 @@ namespace Desktop::View {
void onMap();
void setHidden(bool hidden);
bool isHidden() const;
bool shouldBlur() const;
bool isAllowedOverFullscreen() const;
bool isBlockedByFullscreen() const;
bool isFadingOutUnderFullscreen() const;
@@ -229,7 +230,7 @@ namespace Desktop::View {
void deactivateGroupMembers();
bool isNotResponding();
bool priorityFocus();
SP<CWLSurfaceResource> getSolitaryResource();
SP<CWLSurfaceResource> getSolitaryResource() const;
std::optional<Vector2D> calculateExpression(const Math::SExpressionVec2& expr);
std::optional<Vector2D> minSize();
std::optional<Vector2D> maxSize();
@@ -215,7 +215,7 @@ void CWindowPresentation::invalidateBorderSize() {
m_borderDecoration->invalidateBorderSize();
}
bool CWindowPresentation::opaque() {
bool CWindowPresentation::opaque() const {
if (alphaValue(WINDOW_ALPHA_FADE) != 1.f || alphaValue(WINDOW_ALPHA_FULLSCREEN) != 1.f || alphaValue(WINDOW_ALPHA_ACTIVE) != 1.f)
return false;
@@ -49,7 +49,7 @@ namespace Desktop::View {
int borderSize() const;
void invalidateBorderSize();
bool opaque();
bool opaque() const;
float rounding();
float roundingPower();
bool isInCurvedCorner(double x, double y);
+35 -11
View File
@@ -32,6 +32,7 @@ void IElementRenderer::drawElement(WP<IPassElement> element, const CRegion& dama
case EK_TEXTURE: drawTex(dynamicPointerCast<CTexPassElement>(element), damage); break;
case EK_TEXTURE_MATTE: drawTexMatte(dynamicPointerCast<CTextureMatteElement>(element), damage); break;
case EK_TRANSFORMED_WINDOW: drawTransformedWindow(dynamicPointerCast<CTransformedWindowPassElement>(element), damage); break;
case EK_BACKDROP_SCOPE: drawCustom(element, damage); break;
case EK_CUSTOM: drawCustom(element, damage); break;
default: Log::logger->log(Log::WARN, "Unimplimented draw for {}", element->passName());
}
@@ -328,7 +329,10 @@ void IElementRenderer::drawSurface(WP<CSurfacePassElement> element, const CRegio
// is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back
// to what we do for misaligned surfaces (blur the entire thing and then render shit without blur)
if (m_data.surfaceCounter == 0 && !m_data.popup) {
if (BLUR)
if (BLUR) {
CBox blurPatternBox = {m_data.pos.x - m_data.pMonitor->m_position.x, m_data.pos.y - m_data.pMonitor->m_position.y, m_data.w, m_data.h};
blurPatternBox.scale(m_data.pMonitor->m_scale).round();
drawElement(makeShared<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
@@ -338,6 +342,7 @@ void IElementRenderer::drawSurface(WP<CSurfacePassElement> element, const CRegio
.round = rounding,
.roundingPower = roundingPower,
.blur = true,
.blurPatternBox = blurPatternBox,
.blockBlurOptimization = m_data.blockBlurOptimization,
.allowCustomUV = true,
.surface = m_data.surface,
@@ -347,9 +352,11 @@ void IElementRenderer::drawSurface(WP<CSurfacePassElement> element, const CRegio
.discardOpacity = m_data.discardOpacity,
.clipRegion = clipRegion,
.currentLS = m_data.pLS,
.blurOwner = m_data.pWindow,
}),
surfaceDamage());
else
} else
drawElement(makeShared<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
@@ -492,15 +499,29 @@ void IElementRenderer::drawTex(WP<CTexPassElement> element, const CRegion& damag
inverseOpaque = {0, 0, element->m_data.box.width, element->m_data.box.height};
inverseOpaque.scale(m_renderData.pMonitor->m_scale);
element->m_data.blockBlurOptimization = element->m_data.blockBlurOptimization.value_or(false) ||
!g_pHyprRenderer->shouldUseNewBlurOptimizations(element->m_data.currentLS.lock(), m_renderData.currentWindow.lock());
element->m_data.blockBlurOptimization = element->usesLiveBlur();
// vvv TODO: layered blur fbs?
SP<IFramebuffer> blurredFB;
if (element->m_data.blockBlurOptimization.value_or(false)) {
inverseOpaque.translate(box.pos());
m_renderData.renderModif.applyToRegion(inverseOpaque);
inverseOpaque.intersect(element->m_data.damage);
element->m_data.blurredBG = g_pHyprRenderer->blurMainFramebuffer(element->m_data.a, &inverseOpaque);
auto patternBox = element->m_data.blurPatternBox.value_or(box);
m_renderData.renderModif.applyToBox(patternBox);
std::optional<SBlurShape> shape;
if (!element->m_data.blurShapeInvalid) {
auto shapeBox = box;
m_renderData.renderModif.applyToBox(shapeBox);
if (std::abs(shapeBox.rot) < 0.0001F)
shape = SBlurShape{
.box = shapeBox,
.radius = std::max(sc<float>(element->m_data.round), 0.F),
.roundingPower = element->m_data.roundingPower,
};
}
blurredFB = g_pHyprRenderer->blurMainFramebuffer(element->m_data.a, inverseOpaque, {.patternBox = patternBox, .owner = element->m_data.blurOwner, .shape = shape});
element->m_data.blurredBG = blurredFB->getTexture();
} else
element->m_data.blurredBG = m_renderData.pMonitor->resources()->m_blurFB->getTexture();
@@ -800,12 +821,15 @@ void IElementRenderer::drawTransformedWindow(WP<CTransformedWindowPassElement> e
data.useProvidedDamage = true;
if (element->m_data.blur && blurAlphaMatte) {
data.blur = true;
data.forceBlurBlend = true;
data.blockBlurOptimization = true;
data.blurA = element->m_data.blurA;
data.blurAlphaMatte = blurAlphaMatte;
data.discardMode = 0;
data.blur = true;
data.forceBlurBlend = true;
data.blurPatternBox = element->m_data.blurBox;
data.blurShapeInvalid = true;
data.liveBlurOverride = element->m_data.blurUsesLive;
data.blurOwner = element->m_data.window;
data.blurA = element->m_data.blurA;
data.blurAlphaMatte = blurAlphaMatte;
data.discardMode = 0;
}
(void)blurAlphaMatteFB;
+108 -4
View File
@@ -10,18 +10,26 @@
#include "../protocols/core/DataDevice.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../debug/Overlay.hpp"
#include "../desktop/state/WindowState.hpp"
#include "../desktop/view/window/Window.hpp"
#include "../desktop/view/window/WindowPresentation.hpp"
#include "../event/EventBus.hpp"
#include "../output/Monitor.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/SurfacePassElement.hpp"
#include "../debug/log/Logger.hpp"
#include "../protocols/types/ContentType.hpp"
#include "../state/MonitorState.hpp"
#include "OpenGL.hpp"
#include "Renderer.hpp"
#include "./gl/GLElementRenderer.hpp"
#include "./gl/GLFramebuffer.hpp"
#include "./gl/GLTexture.hpp"
#include "./gl/blur/Factory.hpp"
#include "./gl/blur/Provider.hpp"
#include <cstdint>
#include <ranges>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/utils/ScopeGuard.hpp>
@@ -36,7 +44,12 @@ extern "C" {
#include <xf86drm.h>
}
CHyprGLRenderer::CHyprGLRenderer() : IHyprRenderer(), m_elementRenderer(makeUnique<CGLElementRenderer>()) {}
CHyprGLRenderer::CHyprGLRenderer() : IHyprRenderer(), m_elementRenderer(makeUnique<CGLElementRenderer>()) {
refreshBlurProvider();
m_preRenderListener = Event::bus()->m_events.render.pre.listen([this](PHLMONITOR monitor) { preRender(monitor); });
}
CHyprGLRenderer::~CHyprGLRenderer() = default;
IHyprRenderer::eType CHyprGLRenderer::type() {
return RT_GL;
@@ -308,9 +321,100 @@ void CHyprGLRenderer::drawGlow(const CBox& box, int round, float roundingPower,
g_pHyprOpenGL->renderInnerGlow(box, round, roundingPower, range, grad1, grad2, lerp, 0, a);
}
SP<ITexture> CHyprGLRenderer::blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) {
auto src = GLFB(source);
return g_pHyprOpenGL->blurFramebufferWithDamage(a, originalDamage, *src)->getTexture();
SP<IFramebuffer> CHyprGLRenderer::blurFramebuffer(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context) {
RASSERT(m_blur, "Cannot blur without a blur provider");
return m_blur->blur(source, strength, originalDamage, context);
}
void CHyprGLRenderer::refreshBlurProvider() {
static auto PBLURTYPE = CConfigValue<Config::INTEGER>("decoration:blur:variant");
const auto type = sc<eBlurType>(*PBLURTYPE);
if (m_blur && m_blur->type() == type)
return;
m_blur = createBlurProvider(type, *g_pHyprOpenGL);
}
void CHyprGLRenderer::expandBlurDamage(CRegion& damage, float multiplier) const {
RASSERT(m_blur, "Cannot expand blur damage without a blur provider");
m_blur->expandDamage(damage, multiplier);
}
bool CHyprGLRenderer::blurProviderIsAnimated() const {
return m_blur && m_blur->isAnimated();
}
bool CHyprGLRenderer::blurProviderRequiresLiveBlur() const {
return m_blur && m_blur->requiresLiveBlur();
}
void CHyprGLRenderer::preRender(PHLMONITOR pMonitor) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Config::INTEGER>("decoration:blur:new_optimizations");
static auto PBLURXRAY = CConfigValue<Config::INTEGER>("decoration:blur:xray");
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLURNEWOPTIMIZE || !pMonitor->m_blurFBDirty || !*PBLUR)
return;
if (!pMonitor->m_solitaryClient.expired())
return;
auto windowShouldBeBlurred = [](PHLWINDOW pWindow) -> bool {
if (!pWindow || pWindow->m_ruleApplicator->noBlur().valueOrDefault())
return false;
if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall)
return true;
const auto PSURFACE = pWindow->wlSurface()->resource();
const auto PWORKSPACE = pWindow->m_workspace;
const float A = pWindow->presentation().alphaValue(Desktop::View::WINDOW_ALPHA_FADE) * pWindow->presentation().alphaValue(Desktop::View::WINDOW_ALPHA_FULLSCREEN) *
pWindow->presentation().alphaValue(Desktop::View::WINDOW_ALPHA_LAYOUT) * pWindow->presentation().alphaValue(Desktop::View::WINDOW_ALPHA_ACTIVE) *
PWORKSPACE->m_alpha->value();
if (A < 1.F)
return true;
pixman_box32_t surfbox = {0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y};
CRegion inverseOpaque;
CRegion opaqueRegion{PSURFACE->m_current.opaque};
inverseOpaque.set(opaqueRegion).invert(&surfbox).intersect(0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y);
return !inverseOpaque.empty();
};
bool hasWindows = false;
for (const auto& w : Desktop::windowState()->windows()) {
const auto& XRAY_RULE = w->m_ruleApplicator->xray();
const bool XRAY = XRAY_RULE.hasValue() ? XRAY_RULE.valueOrDefault() : *PBLURXRAY;
const bool ON_ACTIVE_WORKSPACE = w->m_workspace && (w->m_workspace == pMonitor->m_activeWorkspace || w->m_workspace == pMonitor->m_activeSpecialWorkspace);
if (!ON_ACTIVE_WORKSPACE || !w->mapped() || !w->acceptsInput() || !w->alphaNonZero() || ((w->isFloating() || w->onSpecialWorkspace()) && !XRAY) ||
!windowShouldBeBlurred(w))
continue;
hasWindows = true;
break;
}
if (!hasWindows) {
for (const auto& m : State::monitorState()->monitors()) {
for (const auto& layer : m->m_layerSurfaceLayers) {
if (std::ranges::any_of(layer, [](const auto& ls) { return ls->m_layerSurface && ls->m_ruleApplicator->xray().valueOrDefault() == 1; })) {
hasWindows = true;
break;
}
}
if (hasWindows)
break;
}
}
if (!hasWindows)
return;
g_pHyprRenderer->damageMonitor(pMonitor);
pMonitor->m_blurFBShouldRender = true;
}
void CHyprGLRenderer::setViewport(int x, int y, int width, int height) {
+11 -2
View File
@@ -4,10 +4,12 @@
#include "render/ElementRenderer.hpp"
namespace Render::GL {
class IGLBlurProvider;
class CHyprGLRenderer : public Render::IHyprRenderer {
public:
CHyprGLRenderer();
~CHyprGLRenderer() = default;
~CHyprGLRenderer();
eType type() override;
void endRender(const std::function<void()>& renderingDoneCallback = {}) override;
@@ -33,7 +35,11 @@ namespace Render::GL {
void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& color, float a) override;
void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2, float lerp,
float a) override;
SP<ITexture> blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) override;
SP<IFramebuffer> blurFramebuffer(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const Render::SBlurContext& context = {}) override;
void refreshBlurProvider() override;
void expandBlurDamage(CRegion& damage, float multiplier = 1.F) const override;
bool blurProviderIsAnimated() const override;
bool blurProviderRequiresLiveBlur() const override;
void setViewport(int x, int y, int width, int height) override;
bool reloadShaders(const std::string& path = "") override;
@@ -41,6 +47,7 @@ namespace Render::GL {
WP<IElementRenderer> elementRenderer() override;
private:
void preRender(PHLMONITOR pMonitor);
void renderOffToMain(SP<IFramebuffer> off) override;
SP<IRenderbuffer> getOrCreateRenderbufferInternal(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) override;
bool beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple = false) override;
@@ -52,6 +59,8 @@ namespace Render::GL {
SP<IRenderbuffer> m_currentRenderbuffer;
UP<IElementRenderer> m_elementRenderer;
UP<IGLBlurProvider> m_blur;
CHyprSignalListener m_preRenderListener;
friend class CHyprOpenGLImpl;
};
+55 -325
View File
@@ -412,8 +412,6 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd >
initDRMFormats();
static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); });
RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!");
static auto addLastPressToHistory = [this](const Vector2D& pos, bool killing, bool touch) {
@@ -940,14 +938,44 @@ 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", "inner_glow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl", "motion_blur.glsl", "gradient.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", "motion_blur.glsl", "gradient.glsl", "glassFinish.glsl", "fluidJar.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", "inner_glow.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",
"frostfinish.frag",
"ripplefinish.frag",
"dropsfinish.frag",
"waterstep.frag",
"waterfinish.frag",
"fluidjarinit.frag",
"fluidjarstep.frag",
"fluidjargraph.frag",
"fluidjartrack.frag",
"fluidjarvisual.frag",
"fluidjarresample.frag",
"fluidjarhistoryresample.frag",
"fluidjartrackingresample.frag",
"fluidjarfinish.frag",
"prismfinish.frag",
"heatshimmerfinish.frag",
"acrylicfinish.frag",
"aurorafinish.frag",
"hazefinish.frag",
};
bool CHyprOpenGLImpl::initShaders(const std::string& path) {
@@ -1060,6 +1088,10 @@ void CHyprOpenGLImpl::blend(bool enabled) {
m_blend = enabled;
}
bool CHyprOpenGLImpl::blendEnabled() const {
return m_blend;
}
void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) {
auto& m_renderData = g_pHyprRenderer->m_renderData;
RASSERT(m_renderData.pMonitor, "Tried to scissor without begin()!");
@@ -1124,7 +1156,21 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol
CRegion damage{g_pHyprRenderer->m_renderData.damage};
damage.intersect(box);
auto blurredBG = data.xray ? g_pHyprRenderer->m_renderData.pMonitor->resources()->m_blurFB->getTexture() : g_pHyprRenderer->blurMainFramebuffer(data.blurA, &damage);
auto patternBox = data.blurPatternBox.value_or(box);
g_pHyprRenderer->m_renderData.renderModif.applyToBox(patternBox);
auto shapeBox = box;
g_pHyprRenderer->m_renderData.renderModif.applyToBox(shapeBox);
std::optional<SBlurShape> shape;
if (std::abs(shapeBox.rot) < 0.0001F)
shape = SBlurShape{
.box = shapeBox,
.radius = std::max(sc<float>(data.round), 0.F),
.roundingPower = data.roundingPower,
};
const bool usePrecomputedBlur = data.xray && !g_pHyprRenderer->blurProviderRequiresLiveBlur();
const auto blurredFB = usePrecomputedBlur ? g_pHyprRenderer->m_renderData.pMonitor->resources()->m_blurFB :
g_pHyprRenderer->blurMainFramebuffer(data.blurA, damage, {.patternBox = patternBox, .owner = data.blurOwner, .shape = shape});
const auto blurredBG = blurredFB->getTexture();
const auto SAVEDRENDERMODIF = g_pHyprRenderer->m_renderData.renderModif;
g_pHyprRenderer->m_renderData.renderModif = {}; // fix shit
@@ -1651,6 +1697,8 @@ void CHyprOpenGLImpl::renderTextureInternal(SP<ITexture> tex, const CBox& box, c
damageClip.intersect(data.clipRegion);
}
damageClip.intersect(*data.damage);
if (!damageClip.empty()) {
damageClip.forEachRect([this](const auto& RECT) {
scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage);
@@ -1815,324 +1863,6 @@ void CHyprOpenGLImpl::renderTextureMatte(SP<ITexture> tex, const CBox& box, SP<I
tex->unbind();
}
static SCMSettings blurIntermediateCMSettings(bool toIntermediate) {
const auto WORKBUFFER = g_pHyprRenderer->workBufferImageDescription();
const auto INTERMEDIATE = getDefaultImageDescription();
auto settings = toIntermediate ? g_pHyprRenderer->getCMSettings(WORKBUFFER, INTERMEDIATE) : g_pHyprRenderer->getCMSettings(INTERMEDIATE, WORKBUFFER);
auto& range = toIntermediate ? settings.dstTFRange : settings.srcTFRange;
range.max = std::max(range.max, sc<float>(WORKBUFFER->value().luminances.max));
return settings;
}
// This probably isn't the fastest
// but it works... well, I guess?
//
// Dual (or more) kawase blur
SP<IFramebuffer> CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) {
TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage");
auto& m_renderData = g_pHyprRenderer->m_renderData;
const auto BLENDBEFORE = m_blend;
blend(false);
setCapStatus(GL_STENCIL_TEST, false);
CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX);
// get the config settings
static auto PBLURSIZE = CConfigValue<Config::INTEGER>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Config::INTEGER>("decoration:blur:passes");
static auto PBLURVIBRANCY = CConfigValue<Config::FLOAT>("decoration:blur:vibrancy");
static auto PBLURVIBRANCYDARKNESS = CConfigValue<Config::FLOAT>("decoration:blur:vibrancy_darkness");
const auto BLUR_PASSES = std::clamp(*PBLURPASSES, sc<int64_t>(1), sc<int64_t>(8));
// prep damage
CRegion damage{*originalDamage};
damage.expand(std::clamp(*PBLURSIZE, sc<int64_t>(1), sc<int64_t>(40)) * pow(2, BLUR_PASSES));
// helper
const auto PMIRRORFB = g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer();
const auto PMIRRORSWAPFB = g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer();
auto currentRenderToFB = PMIRRORFB;
// Begin with base color adjustments - global brightness and contrast
// TODO: make this a part of the first pass maybe to save on a drawcall?
{
static auto PBLURCONTRAST = CConfigValue<Config::FLOAT>("decoration:blur:contrast");
static auto PBLURBRIGHTNESS = CConfigValue<Config::FLOAT>("decoration:blur:brightness");
PMIRRORSWAPFB->bind();
GLFB(PMIRRORSWAPFB)->clearAfterInvalidation();
setActiveTexture(GL_TEXTURE0);
auto currentTex = source.getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
WP<CShader> shader;
// From FB to sRGB
const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription());
if (!skipCM) {
const auto settings = blurIntermediateCMSettings(/* toIntermediate */ true);
shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE, SH_FEAT_CM, settings.sourceTF, settings.targetTF));
passCMUniforms(shader, g_pHyprRenderer->workBufferImageDescription(), getDefaultImageDescription(), false, -1.F, -1, settings);
shader->setUniformFloat(SHADER_SDR_SATURATION,
m_renderData.pMonitor->m_sdrSaturation > 0 &&
g_pHyprRenderer->workBufferImageDescription()->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 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
} else
shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE));
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST);
shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);
shader->setUniformInt(SHADER_TEX, 0);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!damage.empty()) {
damage.forEachRect([this](const auto& RECT) {
scissor(&RECT, false);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
currentRenderToFB = PMIRRORSWAPFB;
}
// declare the draw func
auto drawPass = [&](WP<CShader> shader, ePreparedFragmentShader frag, CRegion* pDamage) {
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
setActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// prep two shaders
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a
if (frag == SH_FRAG_BLUR1) {
shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_transformedSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_transformedSize.y / 2.f));
shader->setUniformInt(SHADER_PASSES, BLUR_PASSES);
shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY);
shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS);
} else
shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_transformedSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_transformedSize.y * 2.f));
shader->setUniformInt(SHADER_TEX, 0);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!pDamage->empty()) {
pDamage->forEachRect([this](const auto& RECT) {
scissor(&RECT, false);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
};
// draw the things.
// first draw is swap -> mirr
PMIRRORFB->bind();
GLFB(PMIRRORFB)->clearAfterInvalidation();
PMIRRORSWAPFB->getTexture()->bind();
// damage region will be scaled, make a temp
CRegion tempDamage{damage};
// and draw
auto shader = useShader(getShaderVariant(SH_FRAG_BLUR1));
for (auto i = 1; i <= BLUR_PASSES; ++i) {
tempDamage = damage.copy().scale(1.f / (1 << i));
drawPass(shader, SH_FRAG_BLUR1, &tempDamage); // down
}
shader = useShader(getShaderVariant(SH_FRAG_BLUR2));
for (auto i = BLUR_PASSES - 1; i >= 0; --i) {
tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big
drawPass(shader, SH_FRAG_BLUR2, &tempDamage); // up
}
// finalize the image
{
static auto PBLURNOISE = CConfigValue<Config::FLOAT>("decoration:blur:noise");
static auto PBLURBRIGHTNESS = CConfigValue<Config::FLOAT>("decoration:blur:brightness");
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
setActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// From FB to sRGB
const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription());
if (!skipCM) {
const auto settings = blurIntermediateCMSettings(/* toIntermediate */ false);
shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH, SH_FEAT_CM, settings.sourceTF, settings.targetTF));
passCMUniforms(shader, getDefaultImageDescription(), g_pHyprRenderer->workBufferImageDescription(), false, -1.F, -1, settings);
shader->setUniformFloat(SHADER_SDR_SATURATION,
m_renderData.pMonitor->m_sdrSaturation > 0 &&
g_pHyprRenderer->workBufferImageDescription()->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 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
} else
shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH));
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE);
shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);
shader->setUniformInt(SHADER_TEX, 0);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!damage.empty()) {
damage.forEachRect([this](const auto& RECT) {
scissor(&RECT, false /* this region is already transformed */);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
}
// finish
PMIRRORFB->getTexture()->unbind();
blend(BLENDBEFORE);
return currentRenderToFB;
}
void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Config::INTEGER>("decoration:blur:new_optimizations");
static auto PBLURXRAY = CConfigValue<Config::INTEGER>("decoration:blur:xray");
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLURNEWOPTIMIZE || !pMonitor->m_blurFBDirty || !*PBLUR)
return;
// ignore if solitary present, nothing to blur
if (!pMonitor->m_solitaryClient.expired())
return;
// check if we need to update the blur fb
// if there are no windows that would benefit from it,
// we will ignore that the blur FB is dirty.
auto windowShouldBeBlurred = [&](PHLWINDOW pWindow) -> bool {
if (!pWindow)
return false;
if (pWindow->m_ruleApplicator->noBlur().valueOrDefault())
return false;
if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall)
return true;
const auto PSURFACE = pWindow->wlSurface()->resource();
const auto PWORKSPACE = pWindow->m_workspace;
const float A = pWindow->presentation().alphaValue(WINDOW_ALPHA_FADE) * pWindow->presentation().alphaValue(WINDOW_ALPHA_FULLSCREEN) *
pWindow->presentation().alphaValue(WINDOW_ALPHA_LAYOUT) * pWindow->presentation().alphaValue(WINDOW_ALPHA_ACTIVE) * PWORKSPACE->m_alpha->value();
if (A >= 1.f) {
// if (PSURFACE->opaque)
// return false;
CRegion inverseOpaque;
pixman_box32_t surfbox = {0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y};
CRegion opaqueRegion{PSURFACE->m_current.opaque};
inverseOpaque.set(opaqueRegion).invert(&surfbox).intersect(0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y);
if (inverseOpaque.empty())
return false;
}
return true;
};
bool hasWindows = false;
for (auto const& w : Desktop::windowState()->windows()) {
if (w->m_workspace == pMonitor->m_activeWorkspace && w->mapped() && w->acceptsInput() && w->alphaNonZero() && (!w->isFloating() || *PBLURXRAY)) {
// check if window is valid
if (!windowShouldBeBlurred(w))
continue;
hasWindows = true;
break;
}
}
for (auto const& m : State::monitorState()->monitors()) {
for (auto const& lsl : m->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
if (!ls->m_layerSurface || ls->m_ruleApplicator->xray().valueOrDefault() != 1)
continue;
// if (ls->layerSurface->surface->opaque && ls->alpha->value() >= 1.f)
// continue;
hasWindows = true;
break;
}
}
}
if (!hasWindows)
return;
g_pHyprRenderer->damageMonitor(pMonitor);
pMonitor->m_blurFBShouldRender = true;
}
void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP<ITexture> tex, const CBox& box, const STextureRenderData& data) {
auto& m_renderData = g_pHyprRenderer->m_renderData;
RASSERT(m_renderData.pMonitor, "Tried to render texture with blur without begin()!");
+24 -24
View File
@@ -51,6 +51,7 @@ namespace Config {
}
namespace Render::GL {
class CDualKawaseBlurProvider;
CBox resolveBlurUV(const CBox& destinationBox, const Vector2D& textureSize);
@@ -152,12 +153,14 @@ namespace Render::GL {
~CHyprOpenGLImpl();
struct SRectRenderData {
const CRegion* damage = nullptr;
int round = 0;
float roundingPower = 2.F;
bool blur = false;
float blurA = 1.F;
bool xray = false;
const CRegion* damage = nullptr;
int round = 0;
float roundingPower = 2.F;
bool blur = false;
float blurA = 1.F;
bool xray = false;
std::optional<CBox> blurPatternBox;
PHLWINDOWREF blurOwner;
};
struct STextureRenderData {
@@ -229,6 +232,7 @@ namespace Render::GL {
void onFramebufferDeleted(GLuint fb);
void blend(bool enabled);
bool blendEnabled() const;
void scissor(const CBox&, bool transform = true);
void scissor(const pixman_box32*, bool transform = true);
@@ -236,8 +240,6 @@ namespace Render::GL {
void destroyMonitorResources(PHLMONITORREF);
void preRender(PHLMONITOR);
bool saveBufferForMirror(const CBox&);
void applyScreenShader(const std::string& path);
@@ -362,22 +364,19 @@ namespace Render::GL {
//
std::optional<std::vector<uint64_t>> getModsForFormat(EGLint format);
// returns the out FB, can be either Mirror or MirrorSwap
SP<IFramebuffer> blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source);
void passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance, const SCMSettings& settings);
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 passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const SCMSettings& settings);
void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
void renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
WP<CShader> renderScreenShaderInternal();
WP<CShader> renderToFBInternal(SP<ITexture> tex, const STextureRenderData& data, eTextureType texType, const CBox& newBox);
void renderTextureInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);
void renderTextureWithBlurInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);
void passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance, const SCMSettings& settings);
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 passCMUniforms(WP<CShader>, const NColorManagement::PImageDescription imageDescription, const SCMSettings& settings);
void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
void renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data);
WP<CShader> renderScreenShaderInternal();
WP<CShader> renderToFBInternal(SP<ITexture> tex, const STextureRenderData& data, eTextureType texType, const CBox& newBox);
void renderTextureInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);
void renderTextureWithBlurInternal(SP<ITexture>, const CBox&, const STextureRenderData& data);
friend class IHyprRenderer;
friend class CHyprGLRenderer;
@@ -385,6 +384,7 @@ namespace Render::GL {
friend class CTexPassElement;
friend class CPreBlurElement;
friend class CSurfacePassElement;
friend class CDualKawaseBlurProvider;
};
inline UP<CHyprOpenGLImpl> g_pHyprOpenGL;
+94 -61
View File
@@ -48,6 +48,7 @@
#include "pass/RectPassElement.hpp"
#include "pass/RendererHintsPassElement.hpp"
#include "pass/SurfacePassElement.hpp"
#include "pass/BackdropScopePassElement.hpp"
#include "../debug/log/Logger.hpp"
#include "../protocols/ColorManagement.hpp"
#include "../protocols/types/ContentType.hpp"
@@ -598,7 +599,7 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
decorate && !pWindow->backend().traits().suggestsNoBorder && Fullscreen::controller()->getFullscreenModes(pWindow).internal != Fullscreen::FSMODE_FULLSCREEN;
renderdata.rounding = standalone || renderdata.dontRound ? 0 : pWindow->presentation().rounding() * pMonitor->m_scale;
renderdata.roundingPower = standalone || renderdata.dontRound ? 2.0f : pWindow->presentation().roundingPower();
renderdata.blur = !standalone && shouldBlur(pWindow);
renderdata.blur = !standalone && !m_bRenderingSnapshot && pWindow->shouldBlur();
renderdata.pWindow = pWindow;
if (standalone) {
@@ -645,7 +646,11 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
const bool TRANSFORMEDWINDOW = pWindow->effects().hasActiveTransformers();
UP<CRenderPass> transformedPass;
UP<CScopeGuard> passRedirect;
const bool windowBlur = renderdata.blur;
const bool windowBlur = renderdata.blur;
const bool windowBlurUsesLive = windowBlur && !shouldUseNewBlurOptimizations(nullptr, pWindow);
const auto backdropScope = makeShared<SBackdropScope>();
addPassElement(makeUnique<CBackdropScopePassElement>(CBackdropScopePassElement::eAction::BEGIN, backdropScope));
if (TRANSFORMEDWINDOW) {
transformedPass = makeUnique<CRenderPass>();
@@ -679,12 +684,14 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
CBox wb = {renderdata.pos.x - pMonitor->m_position.x, renderdata.pos.y - pMonitor->m_position.y, renderdata.w, renderdata.h};
wb.scale(pMonitor->m_scale).round();
CRectPassElement::SRectData data;
data.color = CHyprColor(0, 0, 0, 0);
data.box = wb;
data.round = renderdata.dontRound ? 0 : renderdata.rounding - 1;
data.blur = true;
data.blurA = renderdata.fadeAlpha;
data.xray = shouldUseNewBlurOptimizations(nullptr, pWindow);
data.color = CHyprColor(0, 0, 0, 0);
data.box = wb;
data.round = renderdata.dontRound ? 0 : renderdata.rounding - 1;
data.blur = true;
data.blurA = renderdata.fadeAlpha;
data.xray = shouldUseNewBlurOptimizations(nullptr, pWindow);
data.blurPatternBox = wb;
data.blurOwner = pWindow;
addPassElement(makeUnique<CRectPassElement>(data));
renderdata.blur = false;
}
@@ -740,6 +747,7 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
.currentBox = currentBox,
.blurBox = blurBox,
.blur = windowBlur,
.blurUsesLive = windowBlurUsesLive,
.blurA = renderdata.fadeAlpha,
.blurRound = renderdata.dontRound ? 0 : std::max(renderdata.rounding - 1, 0),
.blurRoundingPower = renderdata.roundingPower,
@@ -751,6 +759,8 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
renderdata.blur = windowBlur;
}
addPassElement(makeUnique<CBackdropScopePassElement>(CBackdropScopePassElement::eAction::END, backdropScope));
}
m_renderData.clipBox = CBox();
@@ -767,7 +777,7 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
static CConfigValue PBLURIGNOREA = CConfigValue<Config::FLOAT>("decoration:blur:popups_ignorealpha");
renderdata.blur = shouldBlur(pWindow->popupHead());
renderdata.blur = !m_bRenderingSnapshot && pWindow->popupHead()->shouldBlur();
if (renderdata.blur) {
renderdata.discardMode |= DISCARD_ALPHA;
@@ -889,7 +899,7 @@ bool IHyprRenderer::preBlurQueued(PHLMONITORREF pMonitor) {
if (!pMonitor)
return false;
return m_renderData.pMonitor->m_blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pMonitor->m_blurFBShouldRender;
return pMonitor->m_blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && pMonitor->m_blurFBShouldRender;
}
SP<ITexture> IHyprRenderer::createTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy) {
@@ -950,7 +960,7 @@ void IHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s
CSurfacePassElement::SRenderData renderdata = {pMonitor, time, REALPOS};
renderdata.fadeAlpha = pLayer->alpha()[LS_ALPHA_FADE]->value();
renderdata.blur = shouldBlur(pLayer);
renderdata.blur = !m_bRenderingSnapshot && pLayer->shouldBlur();
renderdata.surface = pLayer->wlSurface()->resource();
renderdata.decorate = false;
renderdata.w = REALSIZ.x;
@@ -1518,6 +1528,9 @@ bool IHyprRenderer::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindo
if (!getBlurTexture(m_renderData.pMonitor))
return false;
if (blurProviderRequiresLiveBlur())
return false;
if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault())
return false;
@@ -1732,6 +1745,7 @@ void IHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) {
bool IHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP<IHLBuffer> buffer, SP<IFramebuffer> fb, bool simple) {
m_renderPass.clear();
m_backdropCaptures.clear();
clearCMSettingsCache();
m_renderMode = mode;
m_renderData.pMonitor = pMonitor;
@@ -1828,19 +1842,78 @@ Mat3x3 IHyprRenderer::projectBoxToTarget(const CBox& box, std::optional<eTransfo
return OUTPUT_PROJECTION.copy().multiply(getBoxProjection(box, transform));
}
SP<ITexture> IHyprRenderer::blurMainFramebuffer(float a, CRegion* originalDamage) {
if (!m_renderData.currentFB->getTexture()) {
SP<IFramebuffer> IHyprRenderer::blurMainFramebuffer(float strength, const CRegion& originalDamage, const SBlurContext& context) {
const auto renderTarget = m_renderData.currentFB;
const auto blurSource = !m_backdropCaptures.empty() && m_backdropCaptures.back().framebuffer ? m_backdropCaptures.back().framebuffer : renderTarget;
if (!blurSource || !blurSource->getTexture()) {
Log::logger->log(Log::ERR, "BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)");
return m_renderData.pMonitor->resources()->m_blurFB->getTexture(); // return something to sample from at least
return m_renderData.pMonitor->resources()->m_blurFB; // return something to sample from at least
}
auto guard = bindTempFB(m_renderData.currentFB); // blurFramebuffer messes with FB bindings
return blurFramebuffer(m_renderData.currentFB, a, originalDamage);
auto guard = bindTempFB(renderTarget); // blurFramebuffer messes with FB bindings
return blurFramebuffer(blurSource, strength, originalDamage, context);
}
void IHyprRenderer::preBlurForCurrentMonitor(CRegion* fakeDamage) {
void IHyprRenderer::beginBackdropScope(SP<SBackdropScope> scope) {
RASSERT(scope, "Cannot begin a null backdrop scope");
const auto blurredTex = blurMainFramebuffer(1, fakeDamage);
SP<IFramebuffer> backdrop;
if (scope->required && !scope->damage.empty() && m_renderData.currentFB && m_renderData.currentFB->getTexture()) {
backdrop = m_renderData.pMonitor->resources()->getUnusedWorkBuffer();
if (backdrop) {
const auto renderTarget = m_renderData.currentFB;
const auto savedDamage = m_renderData.damage.copy();
const auto savedRenderModif = m_renderData.renderModif;
const auto savedNearest = m_renderData.useNearestNeighbor;
const auto backend = glBackend();
const auto savedBlend = backend && backend->blendEnabled();
{
auto guard = bindTempFB(backdrop);
m_renderData.damage = scope->damage;
m_renderData.renderModif = {};
m_renderData.useNearestNeighbor = true;
blend(false);
renderOffToMain(renderTarget);
blend(savedBlend);
}
m_renderData.damage = savedDamage;
m_renderData.renderModif = savedRenderModif;
m_renderData.useNearestNeighbor = savedNearest;
} else {
static bool warned = false;
if (!warned) {
warned = true;
Log::logger->log(Log::WARN, "Failed to allocate a clean backdrop buffer; live blur will include the current window's rendered content");
}
}
}
m_backdropCaptures.emplace_back(SBackdropCapture{.scope = std::move(scope), .framebuffer = std::move(backdrop)});
}
void IHyprRenderer::endBackdropScope(SP<SBackdropScope> scope) {
RASSERT(!m_backdropCaptures.empty() && m_backdropCaptures.back().scope == scope, "Unbalanced runtime backdrop scope");
m_backdropCaptures.pop_back();
}
void IHyprRenderer::scheduleFrameForAnimatedBlur(const CRegion& damage, bool usesPrecomputedBlur) {
const auto monitor = m_renderData.pMonitor;
if (m_renderMode != RENDER_MODE_NORMAL || !monitor || monitor->isMirror() || damage.empty())
return;
if (usesPrecomputedBlur)
monitor->m_blurFBDirty = true;
monitor->addDamage(damage);
}
void IHyprRenderer::preBlurForCurrentMonitor(const CRegion& fakeDamage) {
const auto blurredFB = blurMainFramebuffer(1, fakeDamage);
const auto blurredTex = blurredFB->getTexture();
// render onto blurFB
auto guard = bindTempFB(m_renderData.pMonitor->resources()->m_blurFB);
@@ -1851,9 +1924,9 @@ void IHyprRenderer::preBlurForCurrentMonitor(CRegion* fakeDamage) {
CTexPassElement::SRenderData{
.tex = blurredTex,
.box = CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y},
.damage = *fakeDamage,
.damage = fakeDamage,
},
*fakeDamage); // .noAA = true
fakeDamage); // .noAA = true
}
static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {
@@ -3267,6 +3340,7 @@ void IHyprRenderer::renderFadeouts(PHLMONITOR monitor, Desktop::eFadeoutPlane pl
data.blur = EFFECTS.textureBlur.enabled;
data.blurA = EFFECTS.textureBlur.alpha;
data.forceBlurBlend = EFFECTS.textureBlur.forceBlend;
data.blurShapeInvalid = true;
data.ignoreAlpha = EFFECTS.textureBlur.ignoreAlpha;
data.blockBlurOptimization = EFFECTS.textureBlur.blockBlurOptimization;
@@ -3281,47 +3355,6 @@ NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription()
return m_renderData.pMonitor->workBufferImageDescription();
}
bool IHyprRenderer::shouldBlur(PHLLS ls) {
if (m_bRenderingSnapshot)
return false;
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLUR)
return false;
auto surface = ls->wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return ls->m_ruleApplicator->blur().valueOrDefault();
}
bool IHyprRenderer::shouldBlur(PHLWINDOW w) {
if (m_bRenderingSnapshot)
return false;
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLUR)
return false;
const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->presentation().opaque();
if (DONT_BLUR)
return false;
auto surface = w->wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return true;
}
bool IHyprRenderer::shouldBlur(WP<Desktop::View::CPopup> p) {
static CConfigValue PBLURPOPUPS = CConfigValue<Config::INTEGER>("decoration:blur:popups");
static CConfigValue PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
return *PBLURPOPUPS && *PBLUR;
}
SP<ITexture> IHyprRenderer::renderSplash(const std::function<SP<ITexture>(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth,
const int maxHeight) {
static auto PSPLASHCOLOR = CConfigValue<Config::INTEGER>("misc:col.splash");
+36 -24
View File
@@ -10,6 +10,7 @@
#include <vector>
#include <utility>
#include "OpenGL.hpp"
#include "blur/Provider.hpp"
#include "./SyncFDManager.hpp"
#include "./pass/Pass.hpp"
#include "./pass/BorderPassElement.hpp"
@@ -45,6 +46,7 @@ class CEventLoopTimer;
class CToplevelExportProtocolManager;
class CInputManager;
struct SSessionLockSurface;
struct SBackdropScope;
namespace Screenshare {
class CScreenshareFrame;
};
@@ -187,30 +189,37 @@ namespace Render {
virtual void disableScissor() = 0;
virtual void blend(bool enabled) = 0;
virtual void drawShadow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& color, float a) = 0;
virtual void drawShadow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2,
float lerp, float a) = 0;
virtual void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& color, float a) = 0;
virtual void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2,
float lerp, float a) = 0;
virtual void setViewport(int x, int y, int width, int height) = 0;
virtual void drawShadow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2,
float lerp, float a) = 0;
virtual void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& color, float a) = 0;
virtual void drawGlow(const CBox& box, int round, float roundingPower, int range, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2,
float lerp, float a) = 0;
virtual void setViewport(int x, int y, int width, int height) = 0;
bool preBlurQueued(PHLMONITORREF pMonitor);
void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now);
bool preBlurQueued(PHLMONITORREF pMonitor);
void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now);
void setProjectionType(const Vector2D& fbSize);
void setProjectionType(eRenderProjectionType projectionType);
Mat3x3 getBoxProjection(const CBox& box, std::optional<eTransform> transform = std::nullopt);
Mat3x3 projectBoxToTarget(const CBox& box, std::optional<eTransform> transform = std::nullopt);
void setProjectionType(const Vector2D& fbSize);
void setProjectionType(eRenderProjectionType projectionType);
Mat3x3 getBoxProjection(const CBox& box, std::optional<eTransform> transform = std::nullopt);
Mat3x3 projectBoxToTarget(const CBox& box, std::optional<eTransform> transform = std::nullopt);
SP<ITexture> blurMainFramebuffer(float a, CRegion* originalDamage);
virtual SP<ITexture> blurFramebuffer(SP<IFramebuffer> source, float a, CRegion* originalDamage) = 0;
void preBlurForCurrentMonitor(CRegion* fakeDamage);
SP<IFramebuffer> blurMainFramebuffer(float strength, const CRegion& originalDamage, const Render::SBlurContext& context = {});
void beginBackdropScope(SP<SBackdropScope> scope);
void endBackdropScope(SP<SBackdropScope> scope);
virtual SP<IFramebuffer> blurFramebuffer(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const Render::SBlurContext& context = {}) = 0;
virtual void refreshBlurProvider() = 0;
virtual void expandBlurDamage(CRegion& damage, float multiplier = 1.F) const = 0;
virtual bool blurProviderIsAnimated() const = 0;
virtual bool blurProviderRequiresLiveBlur() const = 0;
void scheduleFrameForAnimatedBlur(const CRegion& damage, bool usesPrecomputedBlur);
void preBlurForCurrentMonitor(const CRegion& fakeDamage);
SCMSettings getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
SP<CWLSurfaceResource> surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1,
bool shouldUseSurface = false);
void clearCMSettingsCache();
virtual bool reloadShaders(const std::string& path = "") = 0;
SCMSettings getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
SP<CWLSurfaceResource> surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1,
bool shouldUseSurface = false);
void clearCMSettingsCache();
virtual bool reloadShaders(const std::string& path = "") = 0;
protected:
virtual void renderOffToMain(SP<IFramebuffer> off) = 0;
@@ -277,10 +286,6 @@ namespace Render {
ASP<Hyprgraphics::CImageResource> m_backgroundResource;
bool m_backgroundResourceFailed = false;
bool shouldBlur(PHLLS ls);
bool shouldBlur(PHLWINDOW w);
bool shouldBlur(WP<Desktop::View::CPopup> p);
bool m_cursorHidden = false;
bool m_cursorHiddenByCondition = false;
bool m_cursorHasSurface = false;
@@ -302,6 +307,13 @@ namespace Render {
std::vector<PHLWINDOWREF> m_renderUnfocused;
SP<CEventLoopTimer> m_renderUnfocusedTimer;
struct SBackdropCapture {
SP<SBackdropScope> scope;
SP<IFramebuffer> framebuffer;
};
std::vector<SBackdropCapture> m_backdropCaptures;
friend class CRenderPass;
friend class Render::GL::CHyprOpenGLImpl;
friend class CToplevelExportFrame;
+107 -40
View File
@@ -183,46 +183,113 @@ void CShader::getUniformLocations() {
fullSize = getUniform("screenSize");
m_uniformLocations[SHADER_FULL_SIZE] = fullSize;
m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = getUniform("fullSizeUntransformed");
m_uniformLocations[SHADER_RADIUS] = getUniform("radius");
m_uniformLocations[SHADER_RADIUS_OUTER] = getUniform("radiusOuter");
m_uniformLocations[SHADER_ROUNDING_POWER] = getUniform("roundingPower");
m_uniformLocations[SHADER_THICK] = getUniform("thick");
m_uniformLocations[SHADER_HALFPIXEL] = getUniform("halfpixel");
m_uniformLocations[SHADER_RANGE] = getUniform("range");
m_uniformLocations[SHADER_SHADOW_POWER] = getUniform("shadowPower");
m_uniformLocations[SHADER_USE_ALPHA_MATTE] = getUniform("useAlphaMatte");
m_uniformLocations[SHADER_APPLY_TINT] = getUniform("applyTint");
m_uniformLocations[SHADER_TINT] = getUniform("tint");
m_uniformLocations[SHADER_GRADIENT] = getUniform("gradient");
m_uniformLocations[SHADER_GRADIENT_LENGTH] = getUniform("gradientLength");
m_uniformLocations[SHADER_GRADIENT2] = getUniform("gradient2");
m_uniformLocations[SHADER_GRADIENT2_LENGTH] = getUniform("gradient2Length");
m_uniformLocations[SHADER_ANGLE] = getUniform("angle");
m_uniformLocations[SHADER_ANGLE2] = getUniform("angle2");
m_uniformLocations[SHADER_GRADIENT_LERP] = getUniform("gradientLerp");
m_uniformLocations[SHADER_TIME] = getUniform("time");
m_uniformLocations[SHADER_DISTORT] = getUniform("distort");
m_uniformLocations[SHADER_WL_OUTPUT] = getUniform("wl_output");
m_uniformLocations[SHADER_CONTRAST] = getUniform("contrast");
m_uniformLocations[SHADER_PASSES] = getUniform("passes");
m_uniformLocations[SHADER_VIBRANCY] = getUniform("vibrancy");
m_uniformLocations[SHADER_VIBRANCY_DARKNESS] = getUniform("vibrancy_darkness");
m_uniformLocations[SHADER_BRIGHTNESS] = getUniform("brightness");
m_uniformLocations[SHADER_NOISE] = getUniform("noise");
m_uniformLocations[SHADER_POINTER] = getUniform("pointer_position");
m_uniformLocations[SHADER_POINTER_SHAPE] = getUniform("pointer_shape");
m_uniformLocations[SHADER_POINTER_SWITCH_TIME] = getUniform("pointer_switch_time");
m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = getUniform("pointer_shape_previous");
m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform("pointer_pressed_positions");
m_uniformLocations[SHADER_POINTER_HIDDEN] = getUniform("pointer_hidden");
m_uniformLocations[SHADER_POINTER_KILLING] = getUniform("pointer_killing");
m_uniformLocations[SHADER_POINTER_PRESSED_TIMES] = getUniform("pointer_pressed_times");
m_uniformLocations[SHADER_POINTER_PRESSED_KILLED] = getUniform("pointer_pressed_killed");
m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = getUniform("pointer_pressed_touched");
m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = getUniform("pointer_inactive_timeout");
m_uniformLocations[SHADER_POINTER_LAST_ACTIVE] = getUniform("pointer_last_active");
m_uniformLocations[SHADER_POINTER_SIZE] = getUniform("pointer_size");
m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = getUniform("fullSizeUntransformed");
m_uniformLocations[SHADER_RADIUS] = getUniform("radius");
m_uniformLocations[SHADER_RADIUS_OUTER] = getUniform("radiusOuter");
m_uniformLocations[SHADER_ROUNDING_POWER] = getUniform("roundingPower");
m_uniformLocations[SHADER_THICK] = getUniform("thick");
m_uniformLocations[SHADER_HALFPIXEL] = getUniform("halfpixel");
m_uniformLocations[SHADER_RANGE] = getUniform("range");
m_uniformLocations[SHADER_SHADOW_POWER] = getUniform("shadowPower");
m_uniformLocations[SHADER_USE_ALPHA_MATTE] = getUniform("useAlphaMatte");
m_uniformLocations[SHADER_APPLY_TINT] = getUniform("applyTint");
m_uniformLocations[SHADER_TINT] = getUniform("tint");
m_uniformLocations[SHADER_GRADIENT] = getUniform("gradient");
m_uniformLocations[SHADER_GRADIENT_LENGTH] = getUniform("gradientLength");
m_uniformLocations[SHADER_GRADIENT2] = getUniform("gradient2");
m_uniformLocations[SHADER_GRADIENT2_LENGTH] = getUniform("gradient2Length");
m_uniformLocations[SHADER_ANGLE] = getUniform("angle");
m_uniformLocations[SHADER_ANGLE2] = getUniform("angle2");
m_uniformLocations[SHADER_GRADIENT_LERP] = getUniform("gradientLerp");
m_uniformLocations[SHADER_TIME] = getUniform("time");
m_uniformLocations[SHADER_DISTORT] = getUniform("distort");
m_uniformLocations[SHADER_WL_OUTPUT] = getUniform("wl_output");
m_uniformLocations[SHADER_CONTRAST] = getUniform("contrast");
m_uniformLocations[SHADER_PASSES] = getUniform("passes");
m_uniformLocations[SHADER_VIBRANCY] = getUniform("vibrancy");
m_uniformLocations[SHADER_VIBRANCY_DARKNESS] = getUniform("vibrancy_darkness");
m_uniformLocations[SHADER_BRIGHTNESS] = getUniform("brightness");
m_uniformLocations[SHADER_NOISE] = getUniform("noise");
m_uniformLocations[SHADER_GLASS_REFRACTION] = getUniform("glassRefraction");
m_uniformLocations[SHADER_GLASS_SIZE] = getUniform("glassSize");
m_uniformLocations[SHADER_GLASS_ROUGHNESS] = getUniform("glassRoughness");
m_uniformLocations[SHADER_GLASS_POSITION] = getUniform("glassPosition");
m_uniformLocations[SHADER_DROPS_POSITION] = getUniform("dropsPosition");
m_uniformLocations[SHADER_SHARP_TEX] = getUniform("sharpTex");
m_uniformLocations[SHADER_RIPPLE_COUNT] = getUniform("rippleCount");
m_uniformLocations[SHADER_RIPPLE_IMPULSES] = getUniform("rippleImpulses[0]");
m_uniformLocations[SHADER_RIPPLE_PARAMS] = getUniform("rippleParams");
m_uniformLocations[SHADER_WATER_ENABLED] = getUniform("waterEnabled");
m_uniformLocations[SHADER_WATER_STATE_TEX] = getUniform("waterStateTex");
m_uniformLocations[SHADER_WATER_TEXEL_SIZE] = getUniform("waterTexelSize");
m_uniformLocations[SHADER_WATER_EXTENT] = getUniform("waterExtent");
m_uniformLocations[SHADER_WATER_REFRACTION] = getUniform("waterRefraction");
m_uniformLocations[SHADER_WATER_PARAMS] = getUniform("waterParams");
m_uniformLocations[SHADER_WATER_IMPULSE_COUNT] = getUniform("waterImpulseCount");
m_uniformLocations[SHADER_WATER_IMPULSES] = getUniform("waterImpulses[0]");
m_uniformLocations[SHADER_FLUIDJAR_PARTICLE_TEX] = getUniform("fluidJarParticleTex");
m_uniformLocations[SHADER_FLUIDJAR_GRAPH_TEX] = getUniform("fluidJarGraphTex");
m_uniformLocations[SHADER_FLUIDJAR_TRACKING_TEX] = getUniform("fluidJarTrackingTex");
m_uniformLocations[SHADER_FLUIDJAR_VISUAL_TEX] = getUniform("fluidJarVisualTex");
m_uniformLocations[SHADER_FLUIDJAR_RESOLUTION] = getUniform("fluidJarResolution");
m_uniformLocations[SHADER_FLUIDJAR_GRID_SIZE] = getUniform("fluidJarGridSize");
m_uniformLocations[SHADER_FLUIDJAR_PARTICLE_COUNT] = getUniform("fluidJarParticleCount");
m_uniformLocations[SHADER_FLUIDJAR_FRAME] = getUniform("fluidJarFrame");
m_uniformLocations[SHADER_FLUIDJAR_DT] = getUniform("fluidJarDt");
m_uniformLocations[SHADER_FLUIDJAR_MASS] = getUniform("fluidJarMass");
m_uniformLocations[SHADER_FLUIDJAR_OLD_RESOLUTION] = getUniform("fluidJarOldResolution");
m_uniformLocations[SHADER_FLUIDJAR_OLD_GRID_SIZE] = getUniform("fluidJarOldGridSize");
m_uniformLocations[SHADER_FLUIDJAR_OLD_PARTICLE_COUNT] = getUniform("fluidJarOldParticleCount");
m_uniformLocations[SHADER_FLUIDJAR_TRANSFORM] = getUniform("fluidJarTransform");
m_uniformLocations[SHADER_FLUIDJAR_VELOCITY_SCALE] = getUniform("fluidJarVelocityScale");
m_uniformLocations[SHADER_FLUIDJAR_WALL_VELOCITIES] = getUniform("fluidJarWallVelocities");
m_uniformLocations[SHADER_FLUIDJAR_HISTORY_TEX] = getUniform("fluidJarHistoryTex");
m_uniformLocations[SHADER_FLUIDJAR_HISTORY_TRANSFORM] = getUniform("fluidJarHistoryTransform");
m_uniformLocations[SHADER_FLUIDJAR_HISTORY_FALLBACK] = getUniform("fluidJarHistoryFallback");
m_uniformLocations[SHADER_FLUIDJAR_EXTENT] = getUniform("fluidJarExtent");
m_uniformLocations[SHADER_FLUIDJAR_OUTPUT_TRANSFORM] = getUniform("fluidJarOutputTransform");
m_uniformLocations[SHADER_FLUIDJAR_OUTPUT_OFFSET] = getUniform("fluidJarOutputOffset");
m_uniformLocations[SHADER_FLUIDJAR_LOGICAL_SIZE] = getUniform("fluidJarLogicalSize");
m_uniformLocations[SHADER_FLUIDJAR_COLOR] = getUniform("fluidJarColor");
m_uniformLocations[SHADER_FLUIDJAR_REFRACTION] = getUniform("fluidJarRefraction");
m_uniformLocations[SHADER_FLUIDJAR_TRANSFER_FUNCTION] = getUniform("fluidJarTransferFunction");
m_uniformLocations[SHADER_FLUIDJAR_VISUAL_RESPONSE] = getUniform("fluidJarVisualResponse");
m_uniformLocations[SHADER_FLUIDJAR_STRENGTH] = getUniform("fluidJarStrength");
m_uniformLocations[SHADER_FLUIDJAR_TURBULENCE] = getUniform("fluidJarTurbulence");
m_uniformLocations[SHADER_FLUIDJAR_DISTORTION] = getUniform("fluidJarDistortion");
m_uniformLocations[SHADER_FLUIDJAR_ENABLED] = getUniform("fluidJarEnabled");
m_uniformLocations[SHADER_ACRYLIC_ENABLED] = getUniform("acrylicEnabled");
m_uniformLocations[SHADER_ACRYLIC_EXTENT] = getUniform("acrylicExtent");
m_uniformLocations[SHADER_ACRYLIC_RADIUS] = getUniform("acrylicRadius");
m_uniformLocations[SHADER_ACRYLIC_ROUNDING_POWER] = getUniform("acrylicRoundingPower");
m_uniformLocations[SHADER_ACRYLIC_REFRACTION] = getUniform("acrylicRefraction");
m_uniformLocations[SHADER_ACRYLIC_BULB] = getUniform("acrylicBulb");
m_uniformLocations[SHADER_ACRYLIC_CLARITY] = getUniform("acrylicClarity");
m_uniformLocations[SHADER_ACRYLIC_ABERRATION] = getUniform("acrylicAberration");
m_uniformLocations[SHADER_ACRYLIC_TINT] = getUniform("acrylicTint");
m_uniformLocations[SHADER_ACRYLIC_STRENGTH] = getUniform("acrylicStrength");
m_uniformLocations[SHADER_ACRYLIC_TRANSFER_FUNCTION] = getUniform("acrylicTransferFunction");
m_uniformLocations[SHADER_ACRYLIC_LUMINANCE_SCALE] = getUniform("acrylicLuminanceScale");
m_uniformLocations[SHADER_AURORA_INTENSITY] = getUniform("auroraIntensity");
m_uniformLocations[SHADER_AURORA_COLOR1] = getUniform("auroraColor1");
m_uniformLocations[SHADER_AURORA_COLOR2] = getUniform("auroraColor2");
m_uniformLocations[SHADER_AURORA_TRANSFER_FUNCTION] = getUniform("auroraTransferFunction");
m_uniformLocations[SHADER_HAZE_INTENSITY] = getUniform("hazeIntensity");
m_uniformLocations[SHADER_HAZE_IRIDESCENCE] = getUniform("hazeIridescence");
m_uniformLocations[SHADER_HAZE_TRANSFER_FUNCTION] = getUniform("hazeTransferFunction");
m_uniformLocations[SHADER_POINTER] = getUniform("pointer_position");
m_uniformLocations[SHADER_POINTER_SHAPE] = getUniform("pointer_shape");
m_uniformLocations[SHADER_POINTER_SWITCH_TIME] = getUniform("pointer_switch_time");
m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = getUniform("pointer_shape_previous");
m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform("pointer_pressed_positions");
m_uniformLocations[SHADER_POINTER_HIDDEN] = getUniform("pointer_hidden");
m_uniformLocations[SHADER_POINTER_KILLING] = getUniform("pointer_killing");
m_uniformLocations[SHADER_POINTER_PRESSED_TIMES] = getUniform("pointer_pressed_times");
m_uniformLocations[SHADER_POINTER_PRESSED_KILLED] = getUniform("pointer_pressed_killed");
m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = getUniform("pointer_pressed_touched");
m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = getUniform("pointer_inactive_timeout");
m_uniformLocations[SHADER_POINTER_LAST_ACTIVE] = getUniform("pointer_last_active");
m_uniformLocations[SHADER_POINTER_SIZE] = getUniform("pointer_size");
}
void CShader::createVao() {
+67
View File
@@ -91,6 +91,73 @@ enum eShaderUniform : uint8_t {
SHADER_BLUR_ALPHA_MATTE,
SHADER_BLUR_ALPHA,
SHADER_TONEMAP_MODE,
SHADER_GLASS_REFRACTION,
SHADER_GLASS_SIZE,
SHADER_GLASS_ROUGHNESS,
SHADER_GLASS_POSITION,
SHADER_DROPS_POSITION,
SHADER_SHARP_TEX,
SHADER_RIPPLE_COUNT,
SHADER_RIPPLE_IMPULSES,
SHADER_RIPPLE_PARAMS,
SHADER_WATER_ENABLED,
SHADER_WATER_STATE_TEX,
SHADER_WATER_TEXEL_SIZE,
SHADER_WATER_EXTENT,
SHADER_WATER_REFRACTION,
SHADER_WATER_PARAMS,
SHADER_WATER_IMPULSE_COUNT,
SHADER_WATER_IMPULSES,
SHADER_FLUIDJAR_PARTICLE_TEX,
SHADER_FLUIDJAR_GRAPH_TEX,
SHADER_FLUIDJAR_TRACKING_TEX,
SHADER_FLUIDJAR_VISUAL_TEX,
SHADER_FLUIDJAR_RESOLUTION,
SHADER_FLUIDJAR_GRID_SIZE,
SHADER_FLUIDJAR_PARTICLE_COUNT,
SHADER_FLUIDJAR_FRAME,
SHADER_FLUIDJAR_DT,
SHADER_FLUIDJAR_MASS,
SHADER_FLUIDJAR_OLD_RESOLUTION,
SHADER_FLUIDJAR_OLD_GRID_SIZE,
SHADER_FLUIDJAR_OLD_PARTICLE_COUNT,
SHADER_FLUIDJAR_TRANSFORM,
SHADER_FLUIDJAR_VELOCITY_SCALE,
SHADER_FLUIDJAR_WALL_VELOCITIES,
SHADER_FLUIDJAR_HISTORY_TEX,
SHADER_FLUIDJAR_HISTORY_TRANSFORM,
SHADER_FLUIDJAR_HISTORY_FALLBACK,
SHADER_FLUIDJAR_EXTENT,
SHADER_FLUIDJAR_OUTPUT_TRANSFORM,
SHADER_FLUIDJAR_OUTPUT_OFFSET,
SHADER_FLUIDJAR_LOGICAL_SIZE,
SHADER_FLUIDJAR_COLOR,
SHADER_FLUIDJAR_REFRACTION,
SHADER_FLUIDJAR_TRANSFER_FUNCTION,
SHADER_FLUIDJAR_VISUAL_RESPONSE,
SHADER_FLUIDJAR_STRENGTH,
SHADER_FLUIDJAR_TURBULENCE,
SHADER_FLUIDJAR_DISTORTION,
SHADER_FLUIDJAR_ENABLED,
SHADER_ACRYLIC_ENABLED,
SHADER_ACRYLIC_EXTENT,
SHADER_ACRYLIC_RADIUS,
SHADER_ACRYLIC_ROUNDING_POWER,
SHADER_ACRYLIC_REFRACTION,
SHADER_ACRYLIC_BULB,
SHADER_ACRYLIC_CLARITY,
SHADER_ACRYLIC_ABERRATION,
SHADER_ACRYLIC_TINT,
SHADER_ACRYLIC_STRENGTH,
SHADER_ACRYLIC_TRANSFER_FUNCTION,
SHADER_ACRYLIC_LUMINANCE_SCALE,
SHADER_AURORA_INTENSITY,
SHADER_AURORA_COLOR1,
SHADER_AURORA_COLOR2,
SHADER_AURORA_TRANSFER_FUNCTION,
SHADER_HAZE_INTENSITY,
SHADER_HAZE_IRIDESCENCE,
SHADER_HAZE_TRANSFER_FUNCTION,
SHADER_LAST,
};
+19
View File
@@ -56,6 +56,25 @@ namespace Render {
SH_FRAG_SURFACE,
SH_FRAG_BORDER1,
SH_FRAG_GLITCH,
SH_FRAG_FROSTFINISH,
SH_FRAG_RIPPLEFINISH,
SH_FRAG_DROPSFINISH,
SH_FRAG_WATERSTEP,
SH_FRAG_WATERFINISH,
SH_FRAG_FLUIDJARINIT,
SH_FRAG_FLUIDJARSTEP,
SH_FRAG_FLUIDJARGRAPH,
SH_FRAG_FLUIDJARTRACK,
SH_FRAG_FLUIDJARVISUAL,
SH_FRAG_FLUIDJARRESAMPLE,
SH_FRAG_FLUIDJARHISTORYRESAMPLE,
SH_FRAG_FLUIDJARTRACKINGRESAMPLE,
SH_FRAG_FLUIDJARFINISH,
SH_FRAG_PRISMFINISH,
SH_FRAG_HEATSHIMMERFINISH,
SH_FRAG_ACRYLICFINISH,
SH_FRAG_AURORAFINISH,
SH_FRAG_HAZEFINISH,
SH_FRAG_LAST,
};
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "../../desktop/DesktopTypes.hpp"
#include "../../helpers/math/Math.hpp"
#include "../Framebuffer.hpp"
#include <optional>
namespace Render {
struct SBlurShape {
CBox box;
float radius = 0.F;
float roundingPower = 2.F;
};
struct SBlurContext {
std::optional<CBox> patternBox;
PHLWINDOWREF owner;
std::optional<SBlurShape> shape;
};
enum class eBlurType : uint8_t {
BLUR_DUAL_KAWASE = 0,
BLUR_FROST = 1,
BLUR_RIPPLE = 2,
BLUR_DROPS = 3,
BLUR_WATER = 4,
BLUR_FLUID_JAR = 5,
BLUR_PRISM = 6,
BLUR_HEAT_SHIMMER = 7,
BLUR_ACRYLIC = 8,
BLUR_AURORA = 9,
BLUR_HAZE = 10,
};
// "Jaki kurwa provident????"
class IBlurProvider {
public:
virtual ~IBlurProvider() = default;
virtual eBlurType type() const noexcept = 0;
virtual bool isAnimated() const noexcept = 0;
virtual bool requiresLiveBlur() const noexcept = 0;
virtual void expandDamage(CRegion& damage, float multiplier = 1.F) const = 0;
virtual SP<IFramebuffer> blur(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context = {}) = 0;
protected:
IBlurProvider() = default;
};
};
+8 -2
View File
@@ -77,7 +77,7 @@ void CGLElementRenderer::draw(WP<CFramebufferElement> element, const CRegion& da
void CGLElementRenderer::draw(WP<CPreBlurElement> element, const CRegion& damage) {
auto dmg = damage;
g_pHyprRenderer->preBlurForCurrentMonitor(&dmg);
g_pHyprRenderer->preBlurForCurrentMonitor(dmg);
};
void CGLElementRenderer::draw(WP<CRectPassElement> element, const CRegion& damage) {
@@ -87,7 +87,13 @@ void CGLElementRenderer::draw(WP<CRectPassElement> element, const CRegion& damag
g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.damage = &damage, .round = m_data.round, .roundingPower = m_data.roundingPower});
else
g_pHyprOpenGL->renderRect(m_data.box, m_data.color,
{.round = m_data.round, .roundingPower = m_data.roundingPower, .blur = true, .blurA = m_data.blurA, .xray = m_data.xray});
{.round = m_data.round,
.roundingPower = m_data.roundingPower,
.blur = true,
.blurA = m_data.blurA,
.xray = m_data.xray,
.blurPatternBox = m_data.blurPatternBox,
.blurOwner = m_data.blurOwner});
};
void CGLElementRenderer::draw(WP<CShadowPassElement> element, const CRegion& damage) {
+101
View File
@@ -0,0 +1,101 @@
#include "Acrylic.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../helpers/Color.hpp"
#include "../../../helpers/cm/ColorManagement.hpp"
#include <algorithm>
#include <cmath>
using namespace Render;
using namespace Render::GL;
using namespace NColorManagement;
static constexpr float MAX_ACRYLIC_REFRACTION = 48.F;
static constexpr float MIN_ACRYLIC_BULB = 4.F;
static constexpr float MAX_ACRYLIC_BULB = 256.F;
static float acrylicSampleRadius(float refraction) {
const auto CLAMPED = std::clamp(refraction, 0.F, MAX_ACRYLIC_REFRACTION);
return CLAMPED > 0.F ? std::ceil(CLAMPED + 1.F) : 0.F;
}
static float srgbToLinear(float value) {
return value <= 0.04045F ? value / 12.92F : std::pow((value + 0.055F) / 1.055F, 2.4F);
}
static float acrylicLuminanceScale() {
const auto INTERMEDIATE = getDefaultImageDescription();
const auto WORKBUFFER = g_pHyprRenderer->workBufferImageDescription();
if (!WORKBUFFER)
return 1.F;
const auto MINIMUM = INTERMEDIATE->value().getTFMinLuminance();
const auto MAXIMUM = INTERMEDIATE->value().getTFMaxLuminance();
const auto RANGE = std::max(MAXIMUM, sc<float>(WORKBUFFER->value().luminances.max)) - MINIMUM;
return (MAXIMUM - MINIMUM) / std::max(RANGE, 0.001F);
}
CAcrylicBlurProvider::CAcrylicBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CAcrylicBlurMaterial>()) {
;
}
eBlurType CAcrylicBlurMaterial::type() const noexcept {
return eBlurType::BLUR_ACRYLIC;
}
SBlurMaterialRequirements CAcrylicBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_ACRYLICFINISH,
.preparedInput = true,
.liveBlur = true,
};
}
float CAcrylicBlurMaterial::sampleRadius() const {
static auto PACRYLICREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:acrylic:refraction");
return acrylicSampleRadius(*PACRYLICREFRACTION);
}
void CAcrylicBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
shader->setUniformInt(SHADER_ACRYLIC_ENABLED, 0);
if (!context.blurContext.shape)
return;
const auto extent = context.blurContext.shape->box;
if (extent.width <= 0 || extent.height <= 0)
return;
static auto PACRYLICREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:acrylic:refraction");
static auto PACRYLICBULB = CConfigValue<Config::FLOAT>("decoration:blur:acrylic:bulb");
static auto PACRYLICCLARITY = CConfigValue<Config::FLOAT>("decoration:blur:acrylic:clarity");
static auto PACRYLICABERRATION = CConfigValue<Config::FLOAT>("decoration:blur:acrylic:aberration");
static auto PACRYLICTINT = CConfigValue<Config::INTEGER>("decoration:blur:acrylic:tint");
const auto TINT = CHyprColor(*PACRYLICTINT);
const auto TINT_ALPHA = std::clamp(sc<float>(TINT.a), 0.F, 1.F);
const auto TINT_DEPTH = -std::log(std::max(1.F - TINT_ALPHA, 0.0001F));
const auto LUMINANCE_SCALE = acrylicLuminanceScale();
shader->setUniformInt(SHADER_ACRYLIC_ENABLED, 1);
shader->setUniformFloat4(SHADER_ACRYLIC_EXTENT, sc<float>(extent.x), sc<float>(extent.y), sc<float>(extent.width), sc<float>(extent.height));
shader->setUniformFloat(SHADER_ACRYLIC_RADIUS, std::max(context.blurContext.shape->radius, 0.F));
shader->setUniformFloat(SHADER_ACRYLIC_ROUNDING_POWER, std::max(context.blurContext.shape->roundingPower, 1.F));
shader->setUniformFloat(SHADER_ACRYLIC_REFRACTION, std::clamp(*PACRYLICREFRACTION, 0.F, MAX_ACRYLIC_REFRACTION));
shader->setUniformFloat(SHADER_ACRYLIC_BULB, std::clamp(*PACRYLICBULB, MIN_ACRYLIC_BULB, MAX_ACRYLIC_BULB));
shader->setUniformFloat(SHADER_ACRYLIC_CLARITY, std::clamp(*PACRYLICCLARITY, 0.F, 1.F));
shader->setUniformFloat(SHADER_ACRYLIC_ABERRATION, std::clamp(*PACRYLICABERRATION, 0.F, 0.25F));
shader->setUniformFloat4(SHADER_ACRYLIC_TINT, srgbToLinear(sc<float>(TINT.r)) * LUMINANCE_SCALE, srgbToLinear(sc<float>(TINT.g)) * LUMINANCE_SCALE,
srgbToLinear(sc<float>(TINT.b)) * LUMINANCE_SCALE, TINT_DEPTH);
shader->setUniformFloat(SHADER_ACRYLIC_STRENGTH, std::clamp(context.strength, 0.F, 1.F));
shader->setUniformInt(SHADER_ACRYLIC_TRANSFER_FUNCTION, sc<int>(getDefaultImageDescription()->value().transferFunction));
shader->setUniformFloat(SHADER_ACRYLIC_LUMINANCE_SCALE, LUMINANCE_SCALE);
}
float Render::GL::acrylicDamageRadius(int64_t size, int64_t passes, float refraction) {
return dualKawaseDamageRadius(size, passes) + acrylicSampleRadius(refraction);
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "Kawase.hpp"
namespace Render::GL {
class CAcrylicBlurMaterial final : public IGLBlurMaterial {
public:
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
float sampleRadius() const override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
};
class CAcrylicBlurProvider final : public CDualKawaseBlurProvider {
public:
explicit CAcrylicBlurProvider(CHyprOpenGLImpl& impl);
};
float acrylicDamageRadius(int64_t size, int64_t passes, float refraction);
}
+102
View File
@@ -0,0 +1,102 @@
#include "Aurora.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../helpers/Color.hpp"
#include "../../../helpers/cm/ColorManagement.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
using namespace Render;
using namespace Render::GL;
using namespace NColorManagement;
static constexpr float MAX_AURORA_SPEED = 10.F;
static constexpr double AURORA_BASE_SPEED = 0.22;
static constexpr double AURORA_PERIOD = 6.283185307179586;
static float auroraSpeed() {
static auto PAURORASPEED = CConfigValue<Config::FLOAT>("decoration:blur:aurora:speed");
return std::clamp(*PAURORASPEED, 0.F, MAX_AURORA_SPEED);
}
static float srgbToLinear(float value) {
return value <= 0.04045F ? value / 12.92F : std::pow((value + 0.055F) / 1.055F, 2.4F);
}
static float auroraLuminanceScale() {
const auto INTERMEDIATE = getDefaultImageDescription();
const auto WORKBUFFER = g_pHyprRenderer->workBufferImageDescription();
if (!WORKBUFFER)
return 1.F;
const auto MINIMUM = INTERMEDIATE->value().getTFMinLuminance();
const auto MAXIMUM = INTERMEDIATE->value().getTFMaxLuminance();
const auto RANGE = std::max(MAXIMUM, sc<float>(WORKBUFFER->value().luminances.max)) - MINIMUM;
return (MAXIMUM - MINIMUM) / std::max(RANGE, 0.001F);
}
CAuroraBlurMaterial::CAuroraBlurMaterial() : CGlassBlurMaterial(eBlurType::BLUR_AURORA, SH_FRAG_AURORAFINISH), m_lastAnimationUpdate(Time::steadyNow()) {
m_configListener = Event::bus()->m_events.config.props_refreshed.listen([this](const bool) { updateAnimation(auroraSpeed()); });
}
CAuroraBlurProvider::CAuroraBlurProvider(CHyprOpenGLImpl& impl) : CGlassBlurProvider(impl, makeUnique<CAuroraBlurMaterial>()) {
;
}
bool CAuroraBlurMaterial::isAnimated() const noexcept {
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
static auto PGLASSREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:glass:refraction");
static auto PGLASSROUGHNESS = CConfigValue<Config::FLOAT>("decoration:blur:glass:roughness");
static auto PAURORAINTENSITY = CConfigValue<Config::FLOAT>("decoration:blur:aurora:intensity");
static auto PAURORACOLOR1 = CConfigValue<Config::INTEGER>("decoration:blur:aurora:color1");
static auto PAURORACOLOR2 = CConfigValue<Config::INTEGER>("decoration:blur:aurora:color2");
const auto SPEED = auroraSpeed();
const auto COLOR1 = CHyprColor(*PAURORACOLOR1);
const auto COLOR2 = CHyprColor(*PAURORACOLOR2);
const bool HAS_COLOR = *PAURORAINTENSITY > 0.F && (COLOR1.a > 0.F || COLOR2.a > 0.F);
updateAnimation(SPEED);
return *PBLURENABLED && SPEED > 0.F && (HAS_COLOR || *PGLASSREFRACTION > 0.F || *PGLASSROUGHNESS > 0.F);
}
void CAuroraBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
static auto PAURORAINTENSITY = CConfigValue<Config::FLOAT>("decoration:blur:aurora:intensity");
static auto PAURORACOLOR1 = CConfigValue<Config::INTEGER>("decoration:blur:aurora:color1");
static auto PAURORACOLOR2 = CConfigValue<Config::INTEGER>("decoration:blur:aurora:color2");
CGlassBlurMaterial::bindFinish(shader, context);
updateAnimation(auroraSpeed());
const auto COLOR1 = CHyprColor(*PAURORACOLOR1);
const auto COLOR2 = CHyprColor(*PAURORACOLOR2);
const auto SCALE = auroraLuminanceScale();
const auto bindColor = [&](eShaderUniform uniform, const CHyprColor& color) {
const auto ALPHA = sc<float>(color.a);
shader->setUniformFloat4(uniform, srgbToLinear(sc<float>(color.r)) * SCALE * ALPHA, srgbToLinear(sc<float>(color.g)) * SCALE * ALPHA,
srgbToLinear(sc<float>(color.b)) * SCALE * ALPHA, ALPHA);
};
shader->setUniformFloat(SHADER_TIME, animationPhase());
shader->setUniformFloat(SHADER_AURORA_INTENSITY, std::clamp(*PAURORAINTENSITY, 0.F, 1.F) * std::clamp(context.strength, 0.F, 1.F));
bindColor(SHADER_AURORA_COLOR1, COLOR1);
bindColor(SHADER_AURORA_COLOR2, COLOR2);
shader->setUniformInt(SHADER_AURORA_TRANSFER_FUNCTION, sc<int>(getDefaultImageDescription()->value().transferFunction));
}
void CAuroraBlurMaterial::updateAnimation(float speed) const {
const auto NOW = Time::steadyNow();
m_animationTime += std::chrono::duration<double>(NOW - m_lastAnimationUpdate).count() * m_previousSpeed;
m_lastAnimationUpdate = NOW;
m_previousSpeed = speed;
}
float CAuroraBlurMaterial::animationPhase() const {
return sc<float>(std::fmod(m_animationTime * AURORA_BASE_SPEED, AURORA_PERIOD));
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Glass.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
namespace Render::GL {
class CAuroraBlurMaterial final : public CGlassBlurMaterial {
public:
CAuroraBlurMaterial();
bool isAnimated() const noexcept override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
void updateAnimation(float speed) const;
float animationPhase() const;
mutable Time::steady_tp m_lastAnimationUpdate;
mutable double m_animationTime = 0.0;
mutable float m_previousSpeed = 0.F;
CHyprSignalListener m_configListener;
};
class CAuroraBlurProvider final : public CGlassBlurProvider {
public:
explicit CAuroraBlurProvider(CHyprOpenGLImpl& impl);
};
}
+66
View File
@@ -0,0 +1,66 @@
#include "Drops.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../event/EventBus.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
using namespace Render;
using namespace Render::GL;
static constexpr float MAX_DROPS_SPEED = 10.F;
static constexpr double DROPS_BASE_SPEED = 0.055;
static constexpr double DROPS_PATTERN_PERIOD = 256.0;
static float dropsSpeed() {
static auto PDROPSSPEED = CConfigValue<Config::FLOAT>("decoration:blur:drops:speed");
return std::clamp(*PDROPSSPEED, 0.F, MAX_DROPS_SPEED);
}
CDropsBlurMaterial::CDropsBlurMaterial() : CGlassBlurMaterial(eBlurType::BLUR_DROPS, SH_FRAG_DROPSFINISH, true), m_lastAnimationUpdate(Time::steadyNow()) {
m_configListener = Event::bus()->m_events.config.props_refreshed.listen([this](const bool) { updateAnimation(dropsSpeed()); });
}
CDropsBlurProvider::CDropsBlurProvider(CHyprOpenGLImpl& impl) : CGlassBlurProvider(impl, makeUnique<CDropsBlurMaterial>()) {
;
}
bool CDropsBlurMaterial::isAnimated() const noexcept {
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
static auto PGLASSREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:glass:refraction");
static auto PGLASSROUGHNESS = CConfigValue<Config::FLOAT>("decoration:blur:glass:roughness");
const auto SPEED = dropsSpeed();
updateAnimation(SPEED);
return *PBLURENABLED && SPEED > 0.F && (*PGLASSREFRACTION > 0.F || *PGLASSROUGHNESS > 0.F);
}
void CDropsBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
CGlassBlurMaterial::bindFinish(shader, context);
updateAnimation(dropsSpeed());
shader->setUniformFloat(SHADER_TIME, animationPhase());
const CBox patternBox = context.blurContext.patternBox.value_or(CBox{});
shader->setUniformFloat2(SHADER_DROPS_POSITION, sc<float>(patternBox.x), sc<float>(patternBox.y));
}
void CDropsBlurMaterial::updateAnimation(float speed) const {
const auto NOW = Time::steadyNow();
m_animationTime += std::chrono::duration<double>(NOW - m_lastAnimationUpdate).count() * m_previousSpeed;
m_lastAnimationUpdate = NOW;
m_previousSpeed = speed;
}
float CDropsBlurMaterial::animationPhase() const {
if (m_previousSpeed <= 0.F)
return 0.F;
const auto PHASE = sc<float>(std::fmod(m_animationTime * DROPS_BASE_SPEED, DROPS_PATTERN_PERIOD));
return std::max(PHASE, 0.00002F);
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "Glass.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
namespace Render::GL {
class CDropsBlurMaterial final : public CGlassBlurMaterial {
public:
CDropsBlurMaterial();
bool isAnimated() const noexcept override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
void updateAnimation(float speed) const;
float animationPhase() const;
mutable Time::steady_tp m_lastAnimationUpdate;
mutable double m_animationTime = 0.0;
mutable float m_previousSpeed = 0.F;
CHyprSignalListener m_configListener;
};
class CDropsBlurProvider final : public CGlassBlurProvider {
public:
explicit CDropsBlurProvider(CHyprOpenGLImpl& impl);
};
}
+37
View File
@@ -0,0 +1,37 @@
#include "Factory.hpp"
#include "Acrylic.hpp"
#include "Aurora.hpp"
#include "Drops.hpp"
#include "FluidJar.hpp"
#include "Glass.hpp"
#include "Haze.hpp"
#include "HeatShimmer.hpp"
#include "Kawase.hpp"
#include "Prism.hpp"
#include "Ripple.hpp"
#include "Water.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../debug/log/Logger.hpp"
using namespace Render;
using namespace Render::GL;
UP<IGLBlurProvider> Render::GL::createBlurProvider(eBlurType type, CHyprOpenGLImpl& impl) {
switch (type) {
case eBlurType::BLUR_DUAL_KAWASE: return makeUnique<CDualKawaseBlurProvider>(impl);
case eBlurType::BLUR_FROST: return makeUnique<CGlassBlurProvider>(impl, type, SH_FRAG_FROSTFINISH);
case eBlurType::BLUR_RIPPLE: return makeUnique<CRippleBlurProvider>(impl);
case eBlurType::BLUR_DROPS: return makeUnique<CDropsBlurProvider>(impl);
case eBlurType::BLUR_WATER: return makeUnique<CWaterBlurProvider>(impl);
case eBlurType::BLUR_FLUID_JAR: return makeUnique<CFluidJarBlurProvider>(impl);
case eBlurType::BLUR_PRISM: return makeUnique<CPrismBlurProvider>(impl);
case eBlurType::BLUR_HEAT_SHIMMER: return makeUnique<CHeatShimmerBlurProvider>(impl);
case eBlurType::BLUR_ACRYLIC: return makeUnique<CAcrylicBlurProvider>(impl);
case eBlurType::BLUR_AURORA: return makeUnique<CAuroraBlurProvider>(impl);
case eBlurType::BLUR_HAZE: return makeUnique<CHazeBlurProvider>(impl);
}
Log::logger->log(Log::ERR, "Unknown blur provider {}, falling back to dual Kawase", sc<uint8_t>(type));
return makeUnique<CDualKawaseBlurProvider>(impl);
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "../../../helpers/memory/Memory.hpp"
#include "../../blur/Provider.hpp"
namespace Render::GL {
class CHyprOpenGLImpl;
class IGLBlurProvider;
UP<IGLBlurProvider> createBlurProvider(eBlurType type, CHyprOpenGLImpl& impl);
}
+673
View File
@@ -0,0 +1,673 @@
#include "FluidJar.hpp"
#include "Kawase.hpp"
#include "../GLFramebuffer.hpp"
#include "../../OpenGL.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../desktop/view/window/Window.hpp"
#include "../../../desktop/view/window/WindowPresentation.hpp"
#include "../../../desktop/Workspace.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../helpers/Color.hpp"
#include "../../../helpers/cm/ColorManagement.hpp"
#include <algorithm>
#include <cmath>
#include <drm_fourcc.h>
#include <numbers>
#include <ranges>
using namespace Render;
using namespace Render::GL;
using namespace NColorManagement;
static constexpr float BASE_SIMULATION_SCALE = 0.25F;
static constexpr int BASE_SIMULATION_SIDE = 256;
static constexpr float MIN_PRECISION = 0.5F;
static constexpr float MAX_PRECISION = 8.F;
static constexpr int MAX_PARTICLES = 131072;
static constexpr float FIXED_TIMESTEP = 1.F / 60.F;
static constexpr float SOLVER_TIMESTEP = 3.F;
static constexpr float MAX_WALL_SPEED = 1.25F;
static constexpr float MAX_MOTION_INTERVAL = 0.1F;
static constexpr int MAX_SUBSTEPS = 8;
static constexpr int INITIAL_GRAPH_STEPS = 8;
static constexpr int INITIAL_TRACK_STEPS = 4;
static constexpr int INITIAL_VISUAL_STEPS = 4;
static constexpr float MAX_REFRACTION = 8.F;
static constexpr float MAX_TURBULENCE = 5.F;
static constexpr float MAX_DISTORTION = 10.F;
static constexpr double ANIMATION_PERIOD = 200.0 * std::numbers::pi;
static void bindNearestTexture(SP<CGLFramebuffer> buffer, GLenum unit) {
glActiveTexture(unit);
const auto texture = buffer->getTexture();
texture->bind();
texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
static bool geometryChanged(const CBox& lhs, const CBox& rhs) {
return lhs.x != rhs.x || lhs.y != rhs.y || lhs.width != rhs.width || lhs.height != rhs.height;
}
static bool geometryDiscontinuous(const CBox& oldExtent, const CBox& newExtent, float elapsed) {
if (elapsed <= 0.F || elapsed > MAX_MOTION_INTERVAL)
return true;
const auto oldRight = oldExtent.x + oldExtent.width;
const auto newRight = newExtent.x + newExtent.width;
const auto oldBottom = oldExtent.y + oldExtent.height;
const auto newBottom = newExtent.y + newExtent.height;
const auto horizontalDelta = std::max(std::abs(newExtent.x - oldExtent.x), std::abs(newRight - oldRight));
const auto verticalDelta = std::max(std::abs(newExtent.y - oldExtent.y), std::abs(newBottom - oldBottom));
const auto width = std::max({oldExtent.width, newExtent.width, 1.0});
const auto height = std::max({oldExtent.height, newExtent.height, 1.0});
return horizontalDelta > width * 0.75 || verticalDelta > height * 0.75;
}
static CBox renderedWindowBox(PHLWINDOW window) {
if (!window)
return {};
auto position = window->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT) + window->presentation().floatingOffset();
if (!(window->m_state & Desktop::View::WINDOW_STATE_PINNED) && window->m_workspace)
position += window->m_workspace->m_renderOffset->value();
const auto size = window->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT);
return {position.x, position.y, size.x, size.y};
}
CFluidJarBlurMaterial::CFluidJarBlurMaterial(CHyprOpenGLImpl& impl) : m_impl(impl), m_supported(impl.m_exts.EXT_color_buffer_half_float) {
if (!m_supported)
Log::logger->log(Log::WARN, "fluid_jar blur requires GL_EXT_color_buffer_half_float; falling back to Kawase blur");
m_listeners.renderPre = Event::bus()->m_events.render.pre.listen([this](PHLMONITOR) { ++m_frame; });
m_listeners.windowDestroy =
Event::bus()->m_events.window.destroy.listen([this](PHLWINDOWREF window) { std::erase_if(m_states, [&](const auto& state) { return state.window == window; }); });
}
CFluidJarBlurProvider::CFluidJarBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CFluidJarBlurMaterial>(impl)) {
;
}
eBlurType CFluidJarBlurMaterial::type() const noexcept {
return eBlurType::BLUR_FLUID_JAR;
}
SBlurMaterialRequirements CFluidJarBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_FLUIDJARFINISH,
.preparedInput = m_supported,
.liveBlur = m_supported,
};
}
int64_t CFluidJarBlurMaterial::blurSizeForDamage(int64_t size) const {
return m_supported ? size : std::clamp<int64_t>(size, 1, 40);
}
void CFluidJarBlurMaterial::prepare(const SBlurMaterialContext& context) {
if (!m_supported || context.blurContext.owner.expired())
return;
pruneStates();
const auto state = stateForContext(context.blurContext, true);
const auto renderExtent = transformedPatternBox(context.blurContext);
const auto window = context.blurContext.owner.lock();
const auto physicsExtent = renderedWindowBox(window);
if (!state || physicsExtent.width <= 0 || physicsExtent.height <= 0 || renderExtent.width <= 0 || renderExtent.height <= 0 || !g_pHyprRenderer->m_renderData.pMonitor)
return;
updateState(*state, physicsExtent);
}
void CFluidJarBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
const auto state = stateForContext(context.blurContext);
if (!m_supported || !state || !state->visual[state->currentVisual]) {
shader->setUniformInt(SHADER_FLUIDJAR_ENABLED, 0);
return;
}
const auto extent = transformedPatternBox(context.blurContext);
if (extent.width <= 0 || extent.height <= 0) {
shader->setUniformInt(SHADER_FLUIDJAR_ENABLED, 0);
return;
}
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
if (!monitor) {
shader->setUniformInt(SHADER_FLUIDJAR_ENABLED, 0);
return;
}
const auto logicalExtent = context.blurContext.patternBox.value_or(CBox{0, 0, monitor->m_transformedSize.x, monitor->m_transformedSize.y});
const auto outputTransform = fluidJarOutputTransform(HYPRUTILS_TRANSFORM_NORMAL);
glActiveTexture(GL_TEXTURE2);
const auto texture = state->visual[state->currentVisual]->getTexture();
texture->bind();
texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glActiveTexture(GL_TEXTURE0);
static auto PFLUIDCOLOR = CConfigValue<Config::INTEGER>("decoration:blur:fluid_jar:color");
static auto PFLUIDTURBULENCE = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:turbulence");
static auto PFLUIDDISTORTION = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:distortion");
const auto color = CHyprColor(*PFLUIDCOLOR);
const auto animationPhase = sc<float>(std::fmod(state->animationTime, ANIMATION_PERIOD));
shader->setUniformInt(SHADER_FLUIDJAR_ENABLED, 1);
shader->setUniformInt(SHADER_FLUIDJAR_VISUAL_TEX, 2);
shader->setUniformFloat4(SHADER_FLUIDJAR_EXTENT, sc<float>(extent.x), sc<float>(extent.y), sc<float>(extent.width), sc<float>(extent.height));
shader->setUniformFloat4(SHADER_FLUIDJAR_OUTPUT_TRANSFORM, sc<float>(outputTransform.xAxis.x), sc<float>(outputTransform.yAxis.x), sc<float>(outputTransform.xAxis.y),
sc<float>(outputTransform.yAxis.y));
shader->setUniformFloat2(SHADER_FLUIDJAR_OUTPUT_OFFSET, sc<float>(outputTransform.offset.x), sc<float>(outputTransform.offset.y));
shader->setUniformFloat2(SHADER_FLUIDJAR_LOGICAL_SIZE, sc<float>(logicalExtent.width), sc<float>(logicalExtent.height));
shader->setUniformFloat4(SHADER_FLUIDJAR_COLOR, color.r, color.g, color.b, color.a);
shader->setUniformFloat(SHADER_FLUIDJAR_REFRACTION, MAX_REFRACTION);
shader->setUniformInt(SHADER_FLUIDJAR_TRANSFER_FUNCTION, sc<int>(getDefaultImageDescription()->value().transferFunction));
shader->setUniformFloat(SHADER_FLUIDJAR_STRENGTH, std::clamp(context.strength, 0.F, 1.F));
shader->setUniformFloat(SHADER_FLUIDJAR_TURBULENCE, std::clamp(*PFLUIDTURBULENCE, 0.F, MAX_TURBULENCE));
shader->setUniformFloat(SHADER_FLUIDJAR_DISTORTION, std::clamp(*PFLUIDDISTORTION, 0.F, MAX_DISTORTION));
shader->setUniformFloat(SHADER_TIME, animationPhase);
}
float CFluidJarBlurMaterial::sampleRadius() const {
if (!m_supported)
return 0.F;
static auto PDISTORTION = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:distortion");
return std::ceil(MAX_REFRACTION * std::clamp(*PDISTORTION, 0.F, MAX_DISTORTION));
}
CFluidJarBlurMaterial::SState* CFluidJarBlurMaterial::stateForContext(const SBlurContext& context, bool create) {
if (context.owner.expired())
return nullptr;
const auto state = std::ranges::find_if(m_states, [&](const auto& candidate) { return candidate.window == context.owner; });
if (state != m_states.end())
return &*state;
if (!create)
return nullptr;
return &m_states.emplace_back(SState{.window = context.owner});
}
const CFluidJarBlurMaterial::SState* CFluidJarBlurMaterial::stateForContext(const SBlurContext& context) const {
if (context.owner.expired())
return nullptr;
const auto state = std::ranges::find_if(m_states, [&](const auto& candidate) { return candidate.window == context.owner; });
return state != m_states.end() ? &*state : nullptr;
}
void CFluidJarBlurMaterial::updateState(SState& state, const CBox& extent) {
if (state.lastFrame == m_frame)
return;
static auto PFLUIDSPEED = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:speed");
static auto PFLUIDFILL = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:fill_amount");
static auto PPRECISION = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:precision");
const auto precision = std::clamp(*PPRECISION, MIN_PRECISION, MAX_PRECISION);
const auto simulationSize = fluidJarSimulationSize(extent.size(), precision);
const auto fillAmount = std::clamp(*PFLUIDFILL, 0.F, 1.F);
if (simulationSize.x <= 0 || simulationSize.y <= 0)
return;
const auto now = Time::steadyNow();
const auto elapsed = state.lastUpdate == Time::steady_tp{} ? FIXED_TIMESTEP : std::clamp(std::chrono::duration<float>(now - state.lastUpdate).count(), 0.F, 0.5F);
const auto speed = std::clamp(*PFLUIDSPEED, 0.F, 10.F);
if (!state.particles[0] || state.fillAmount != fillAmount || state.precision != precision) {
initializeState(state, simulationSize, fillAmount, precision);
state.extent = extent;
state.hasExtent = true;
} else if (!state.hasExtent) {
state.extent = extent;
state.hasExtent = true;
} else if (state.simulationSize != simulationSize || geometryChanged(state.extent, extent)) {
const auto discontinuous = geometryDiscontinuous(state.extent, extent, elapsed);
const auto transform = fluidJarGeometryTransform(state.extent, extent, state.simulationSize, simulationSize, !discontinuous);
state.wallVelocities = discontinuous ? std::array<float, 4>{} : fluidJarWallVelocities(state.extent, extent, simulationSize, elapsed, speed);
transformState(state, simulationSize, transform, state.wallVelocities);
state.extent = extent;
} else
state.wallVelocities = {};
if (speed > 0.F && state.particleCount > 0) {
state.animationTime += std::min(elapsed, 0.05F) * speed;
state.accumulator += std::min(elapsed, 0.05F) * speed;
int substeps = 0;
while (state.accumulator >= FIXED_TIMESTEP && substeps < MAX_SUBSTEPS) {
drawParticleStep(state, SOLVER_TIMESTEP);
drawGraphStep(state);
drawTrackingStep(state);
state.accumulator -= FIXED_TIMESTEP;
++state.simulationFrame;
++substeps;
}
if (substeps > 0)
drawVisualStep(state, substeps);
if (substeps == MAX_SUBSTEPS)
state.accumulator = std::min(state.accumulator, sc<double>(FIXED_TIMESTEP));
scheduleNextFrame(state);
}
state.lastUpdate = now;
state.lastFrame = m_frame;
}
void CFluidJarBlurMaterial::initializeState(SState& state, const Vector2D& simulationSize, float fillAmount, float precision) {
state.simulationSize = simulationSize;
state.gridSize = fluidJarGridSize(simulationSize);
state.particleTextureSize = {state.gridSize.x * 4.0, state.gridSize.y};
state.graphTextureSize = {state.gridSize.x * 8.0, state.gridSize.y};
allocateBuffers(state.particles, state.particleTextureSize, "Fluid jar particles");
allocateBuffers(state.graph, state.graphTextureSize, "Fluid jar graph", DRM_FORMAT_ABGR16161616);
allocateBuffers(state.tracking, simulationSize, "Fluid jar tracking", DRM_FORMAT_ABGR16161616);
allocateBuffers(state.visual, simulationSize, "Fluid jar visual");
state.fillAmount = fillAmount;
state.precision = precision;
state.particleCount = fluidJarInitialParticleCount(simulationSize, fillAmount);
state.currentParticles = 0;
state.currentGraph = 0;
state.currentTracking = 0;
state.currentVisual = 0;
state.simulationFrame = 0;
state.accumulator = 0.0;
state.animationTime = 0.0;
state.lastUpdate = {};
state.wallVelocities = {};
for (const auto& buffer : state.particles)
drawInitialize(state, buffer);
clearIntegerBuffers(state.graph);
for (int i = 0; i < INITIAL_GRAPH_STEPS; ++i) {
drawGraphStep(state);
++state.simulationFrame;
}
clearIntegerBuffers(state.tracking);
clearBuffers(state.visual, {0.F, 0.F, 0.F, 0.F});
if (state.particleCount > 0) {
for (int i = 0; i < INITIAL_TRACK_STEPS; ++i) {
drawTrackingStep(state);
++state.simulationFrame;
}
for (int i = 0; i < INITIAL_VISUAL_STEPS; ++i)
drawVisualStep(state);
}
}
void CFluidJarBlurMaterial::transformState(SState& state, const Vector2D& simulationSize, const SFluidJarGeometryTransform& transform, const std::array<float, 4>& wallVelocities) {
const auto oldSize = state.simulationSize;
const auto oldGridSize = state.gridSize;
const auto oldParticleCount = state.particleCount;
const auto oldParticles = state.particles[state.currentParticles];
const auto oldTracking = state.tracking[state.currentTracking];
const auto oldVisual = state.visual[state.currentVisual];
const bool resized = oldSize != simulationSize;
state.simulationSize = simulationSize;
state.particleCount = fluidJarResizedParticleCount(oldParticleCount, simulationSize);
const auto particleTarget = state.particles[1 - state.currentParticles];
drawResample(state, oldParticles, particleTarget, oldGridSize, oldParticleCount, transform, wallVelocities);
state.currentParticles = 1 - state.currentParticles;
drawGraphStep(state);
if (resized) {
std::array<SP<CGLFramebuffer>, 2> tracking;
std::array<SP<CGLFramebuffer>, 2> visual;
allocateBuffers(tracking, simulationSize, "Fluid jar resized tracking", DRM_FORMAT_ABGR16161616);
allocateBuffers(visual, simulationSize, "Fluid jar resized visual");
clearIntegerBuffers(tracking);
clearBuffers(visual, {0.F, 0.F, 0.F, 0.F});
drawTrackingResample(oldTracking, tracking[0], oldSize, transform);
drawHistoryResample(oldVisual, visual[0], oldSize, transform, {0.F, 0.F, 0.F, 0.F}, true);
state.tracking = std::move(tracking);
state.visual = std::move(visual);
state.currentTracking = 0;
state.currentVisual = 0;
} else {
drawTrackingResample(oldTracking, state.tracking[1 - state.currentTracking], oldSize, transform);
drawHistoryResample(oldVisual, state.visual[1 - state.currentVisual], oldSize, transform, {0.F, 0.F, 0.F, 0.F}, true);
state.currentTracking = 1 - state.currentTracking;
state.currentVisual = 1 - state.currentVisual;
}
if (state.particleCount > 0) {
drawTrackingStep(state);
const bool velocityScaleChanged = std::abs(transform.velocityScale.x - 1.0) > 0.001 || std::abs(transform.velocityScale.y - 1.0) > 0.001;
drawVisualStep(state, resized || velocityScaleChanged ? INITIAL_VISUAL_STEPS : 1);
}
}
void CFluidJarBlurMaterial::allocateBuffers(std::array<SP<CGLFramebuffer>, 2>& buffers, const Vector2D& size, const std::string& name, DRMFormat format) const {
for (auto& buffer : buffers) {
if (!buffer)
buffer = dynamicPointerCast<CGLFramebuffer>(g_pHyprRenderer->createFB(name));
buffer->alloc(sc<int>(size.x), sc<int>(size.y), format);
}
}
void CFluidJarBlurMaterial::clearIntegerBuffers(const std::array<SP<CGLFramebuffer>, 2>& buffers) const {
constexpr std::array<GLuint, 4> CLEAR_VALUE = {};
for (const auto& buffer : buffers) {
buffer->bind();
g_pHyprRenderer->disableScissor();
g_pHyprRenderer->blend(false);
glClearBufferuiv(GL_COLOR, 0, CLEAR_VALUE.data());
}
}
void CFluidJarBlurMaterial::clearBuffers(const std::array<SP<CGLFramebuffer>, 2>& buffers, const std::array<float, 4>& color) const {
for (const auto& buffer : buffers) {
buffer->bind();
g_pHyprRenderer->setViewport(0, 0, sc<int>(buffer->m_size.x), sc<int>(buffer->m_size.y));
g_pHyprRenderer->disableScissor();
glClearColor(color[0], color[1], color[2], color[3]);
glClear(GL_COLOR_BUFFER_BIT);
}
}
void CFluidJarBlurMaterial::drawInitialize(const SState& state, SP<CGLFramebuffer> target) const {
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARINIT));
preparePass(target, state.particleTextureSize, shader);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
void CFluidJarBlurMaterial::drawResample(const SState& state, SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldGridSize, int oldParticleCount,
const SFluidJarGeometryTransform& transform, const std::array<float, 4>& wallVelocities) const {
static auto PFLUIDMASS = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:mass");
bindNearestTexture(source, GL_TEXTURE0);
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARRESAMPLE));
preparePass(target, state.particleTextureSize, shader);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_TEX, 0);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
shader->setUniformFloat2(SHADER_FLUIDJAR_OLD_GRID_SIZE, oldGridSize.x, oldGridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_OLD_PARTICLE_COUNT, oldParticleCount);
shader->setUniformFloat4(SHADER_FLUIDJAR_TRANSFORM, transform.positionScale.x, transform.positionScale.y, transform.positionOffset.x, transform.positionOffset.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_VELOCITY_SCALE, transform.velocityScale.x, transform.velocityScale.y);
shader->setUniformFloat4(SHADER_FLUIDJAR_WALL_VELOCITIES, wallVelocities[0], wallVelocities[1], wallVelocities[2], wallVelocities[3]);
shader->setUniformFloat(SHADER_FLUIDJAR_MASS, std::clamp(*PFLUIDMASS, 0.1F, 10.F));
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
void CFluidJarBlurMaterial::drawHistoryResample(SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldSize, const SFluidJarGeometryTransform& transform,
const std::array<float, 4>& fallback, bool linear) const {
glActiveTexture(GL_TEXTURE0);
const auto texture = source->getTexture();
texture->bind();
texture->setTexParameter(GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
texture->setTexParameter(GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
const Vector2D inverseScale = {1.0 / transform.positionScale.x, 1.0 / transform.positionScale.y};
const Vector2D inverseOffset = {-transform.positionOffset.x * inverseScale.x, -transform.positionOffset.y * inverseScale.y};
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARHISTORYRESAMPLE));
preparePass(target, target->m_size, shader);
shader->setUniformInt(SHADER_FLUIDJAR_HISTORY_TEX, 0);
shader->setUniformFloat2(SHADER_FLUIDJAR_OLD_RESOLUTION, oldSize.x, oldSize.y);
shader->setUniformFloat4(SHADER_FLUIDJAR_HISTORY_TRANSFORM, inverseScale.x, inverseScale.y, inverseOffset.x, inverseOffset.y);
shader->setUniformFloat4(SHADER_FLUIDJAR_HISTORY_FALLBACK, fallback[0], fallback[1], fallback[2], fallback[3]);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
void CFluidJarBlurMaterial::drawParticleStep(SState& state, float dt) const {
static auto PFLUIDMASS = CConfigValue<Config::FLOAT>("decoration:blur:fluid_jar:mass");
const auto source = state.particles[state.currentParticles];
const auto target = state.particles[1 - state.currentParticles];
bindNearestTexture(source, GL_TEXTURE0);
bindNearestTexture(state.graph[state.currentGraph], GL_TEXTURE1);
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARSTEP));
preparePass(target, state.particleTextureSize, shader);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_TEX, 0);
shader->setUniformInt(SHADER_FLUIDJAR_GRAPH_TEX, 1);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
shader->setUniformFloat(SHADER_FLUIDJAR_DT, dt);
shader->setUniformFloat(SHADER_FLUIDJAR_MASS, std::clamp(*PFLUIDMASS, 0.1F, 10.F));
shader->setUniformFloat4(SHADER_FLUIDJAR_WALL_VELOCITIES, state.wallVelocities[0], state.wallVelocities[1], state.wallVelocities[2], state.wallVelocities[3]);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
state.currentParticles = 1 - state.currentParticles;
}
void CFluidJarBlurMaterial::drawGraphStep(SState& state) const {
const auto target = state.graph[1 - state.currentGraph];
bindNearestTexture(state.particles[state.currentParticles], GL_TEXTURE0);
bindNearestTexture(state.graph[state.currentGraph], GL_TEXTURE1);
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARGRAPH));
preparePass(target, state.graphTextureSize, shader);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_TEX, 0);
shader->setUniformInt(SHADER_FLUIDJAR_GRAPH_TEX, 1);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
shader->setUniformInt(SHADER_FLUIDJAR_FRAME, sc<int>(state.simulationFrame));
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
state.currentGraph = 1 - state.currentGraph;
}
void CFluidJarBlurMaterial::drawTrackingStep(SState& state) const {
const auto target = state.tracking[1 - state.currentTracking];
bindNearestTexture(state.particles[state.currentParticles], GL_TEXTURE0);
bindNearestTexture(state.graph[state.currentGraph], GL_TEXTURE1);
bindNearestTexture(state.tracking[state.currentTracking], GL_TEXTURE2);
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARTRACK));
preparePass(target, state.simulationSize, shader);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_TEX, 0);
shader->setUniformInt(SHADER_FLUIDJAR_GRAPH_TEX, 1);
shader->setUniformInt(SHADER_FLUIDJAR_TRACKING_TEX, 2);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
shader->setUniformInt(SHADER_FLUIDJAR_FRAME, sc<int>(state.simulationFrame));
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
state.currentTracking = 1 - state.currentTracking;
}
void CFluidJarBlurMaterial::drawTrackingResample(SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldSize, const SFluidJarGeometryTransform& transform) const {
bindNearestTexture(source, GL_TEXTURE0);
const Vector2D inverseScale = {1.0 / transform.positionScale.x, 1.0 / transform.positionScale.y};
const Vector2D inverseOffset = {-transform.positionOffset.x * inverseScale.x, -transform.positionOffset.y * inverseScale.y};
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARTRACKINGRESAMPLE));
preparePass(target, target->m_size, shader);
shader->setUniformInt(SHADER_FLUIDJAR_HISTORY_TEX, 0);
shader->setUniformFloat2(SHADER_FLUIDJAR_OLD_RESOLUTION, oldSize.x, oldSize.y);
shader->setUniformFloat4(SHADER_FLUIDJAR_HISTORY_TRANSFORM, inverseScale.x, inverseScale.y, inverseOffset.x, inverseOffset.y);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
void CFluidJarBlurMaterial::drawVisualStep(SState& state, int steps) const {
const auto target = state.visual[1 - state.currentVisual];
bindNearestTexture(state.particles[state.currentParticles], GL_TEXTURE0);
bindNearestTexture(state.graph[state.currentGraph], GL_TEXTURE1);
bindNearestTexture(state.tracking[state.currentTracking], GL_TEXTURE2);
bindNearestTexture(state.visual[state.currentVisual], GL_TEXTURE3);
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_FLUIDJARVISUAL));
preparePass(target, state.simulationSize, shader);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_TEX, 0);
shader->setUniformInt(SHADER_FLUIDJAR_GRAPH_TEX, 1);
shader->setUniformInt(SHADER_FLUIDJAR_TRACKING_TEX, 2);
shader->setUniformInt(SHADER_FLUIDJAR_VISUAL_TEX, 3);
shader->setUniformFloat2(SHADER_FLUIDJAR_RESOLUTION, state.simulationSize.x, state.simulationSize.y);
shader->setUniformFloat2(SHADER_FLUIDJAR_GRID_SIZE, state.gridSize.x, state.gridSize.y);
shader->setUniformInt(SHADER_FLUIDJAR_PARTICLE_COUNT, state.particleCount);
shader->setUniformFloat(SHADER_FLUIDJAR_VISUAL_RESPONSE, sc<float>(std::max(steps, 1)));
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
state.currentVisual = 1 - state.currentVisual;
glActiveTexture(GL_TEXTURE0);
}
void CFluidJarBlurMaterial::preparePass(SP<CGLFramebuffer> target, const Vector2D& size, WP<CShader> shader) const {
target->bind();
g_pHyprRenderer->setViewport(0, 0, sc<int>(size.x), sc<int>(size.y));
g_pHyprRenderer->disableScissor();
g_pHyprRenderer->blend(false);
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
const auto matrix = g_pHyprRenderer->projectBoxToTarget({0, 0, monitor->m_transformedSize.x, monitor->m_transformedSize.y});
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, matrix.getMatrix());
}
CBox CFluidJarBlurMaterial::transformedPatternBox(const SBlurContext& context) const {
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
if (!monitor)
return {};
return context.patternBox.value_or(CBox{0, 0, monitor->m_transformedSize.x, monitor->m_transformedSize.y});
}
void CFluidJarBlurMaterial::scheduleNextFrame(const SState& state) const {
const auto window = state.window.lock();
if (!window)
return;
g_pHyprRenderer->damageWindow(window);
}
void CFluidJarBlurMaterial::pruneStates() {
std::erase_if(m_states, [](const auto& state) { return state.window.expired() || !state.window->shouldBlur(); });
}
Vector2D Render::GL::fluidJarSimulationSize(const Vector2D& extent, float precision) {
if (extent.x <= 0 || extent.y <= 0)
return {};
const auto clampedPrecision = std::clamp(precision, MIN_PRECISION, MAX_PRECISION);
const auto simulationScale = BASE_SIMULATION_SCALE * clampedPrecision;
const auto maxSide = BASE_SIMULATION_SIDE * clampedPrecision;
const auto scale = std::min({sc<double>(simulationScale), sc<double>(maxSide) / extent.x, sc<double>(maxSide) / extent.y});
Vector2D size = {std::max(8.0, std::floor(extent.x * scale)), std::max(4.0, std::floor(extent.y * scale))};
const auto capacity = fluidJarParticleCapacity(size);
if (capacity > MAX_PARTICLES) {
const auto particleScale = std::sqrt(sc<double>(MAX_PARTICLES) / capacity);
size.x = std::max(8.0, std::floor(size.x * particleScale));
size.y = std::max(4.0, std::floor(size.y * particleScale));
}
while (fluidJarParticleCapacity(size) > MAX_PARTICLES) {
if (size.x >= size.y)
size.x -= 1.0;
else
size.y -= 1.0;
}
return size;
}
Vector2D Render::GL::fluidJarGridSize(const Vector2D& simulationSize) {
return {std::max(1.0, std::floor(simulationSize.x * 0.5 / 4.0)), std::max(1.0, std::floor(simulationSize.y * 0.5 / 2.0))};
}
int Render::GL::fluidJarParticleCapacity(const Vector2D& simulationSize) {
const auto grid = fluidJarGridSize(simulationSize);
return sc<int>(grid.x * grid.y);
}
int Render::GL::fluidJarInitialParticleCount(const Vector2D& simulationSize, float fillAmount) {
return sc<int>(std::floor(fluidJarParticleCapacity(simulationSize) * std::clamp(fillAmount, 0.F, 1.F)));
}
int Render::GL::fluidJarResizedParticleCount(int oldParticleCount, const Vector2D&) {
return std::clamp(oldParticleCount, 0, MAX_PARTICLES);
}
float Render::GL::fluidJarDamageRadius(int64_t size, int64_t passes, float distortion) {
return dualKawaseDamageRadius(size, passes) + std::ceil(MAX_REFRACTION * std::clamp(distortion, 0.F, MAX_DISTORTION));
}
SFluidJarOutputTransform Render::GL::fluidJarOutputTransform(eTransform transform) {
constexpr Vector2D UNIT_EXTENT = {1, 1};
const auto offset = Vector2D{}.transform(transform, UNIT_EXTENT);
return {
.xAxis = Vector2D{1, 0}.transform(transform, UNIT_EXTENT) - offset,
.yAxis = Vector2D{0, 1}.transform(transform, UNIT_EXTENT) - offset,
.offset = offset,
};
}
SFluidJarGeometryTransform Render::GL::fluidJarGeometryTransform(const CBox& oldExtent, const CBox& newExtent, const Vector2D& oldSimulationSize, const Vector2D& newSimulationSize,
bool preserveWorldPosition) {
if (oldExtent.width <= 0 || oldExtent.height <= 0 || newExtent.width <= 0 || newExtent.height <= 0 || oldSimulationSize.x <= 0 || oldSimulationSize.y <= 0 ||
newSimulationSize.x <= 0 || newSimulationSize.y <= 0)
return {};
const Vector2D oldScale = {oldSimulationSize.x / oldExtent.width, oldSimulationSize.y / oldExtent.height};
const Vector2D newScale = {newSimulationSize.x / newExtent.width, newSimulationSize.y / newExtent.height};
const Vector2D scale = {newScale.x / oldScale.x, newScale.y / oldScale.y};
if (!preserveWorldPosition)
return {.positionScale = {newSimulationSize.x / oldSimulationSize.x, newSimulationSize.y / oldSimulationSize.y}, .velocityScale = {0, 0}};
const auto oldBottom = oldExtent.y + oldExtent.height;
const auto newBottom = newExtent.y + newExtent.height;
return {
.positionScale = scale,
.positionOffset = {(oldExtent.x - newExtent.x) * newScale.x, (newBottom - oldBottom) * newScale.y},
.velocityScale = scale,
};
}
std::array<float, 4> Render::GL::fluidJarWallVelocities(const CBox& oldExtent, const CBox& newExtent, const Vector2D& simulationSize, float elapsed, float speed) {
if (elapsed <= 0.F || oldExtent.width <= 0 || oldExtent.height <= 0 || newExtent.width <= 0 || newExtent.height <= 0 || simulationSize.x <= 0 || simulationSize.y <= 0)
return {};
if (speed <= 0.F)
return {};
const auto scaleX = simulationSize.x / newExtent.width;
const auto scaleY = simulationSize.y / newExtent.height;
const auto oldRight = oldExtent.x + oldExtent.width;
const auto newRight = newExtent.x + newExtent.width;
const auto oldBottom = oldExtent.y + oldExtent.height;
const auto newBottom = newExtent.y + newExtent.height;
const auto solverScale = FIXED_TIMESTEP / (SOLVER_TIMESTEP * speed);
const auto velocity = [&](double displacement, double scale) { return std::clamp(sc<float>(displacement / elapsed * scale * solverScale), -MAX_WALL_SPEED, MAX_WALL_SPEED); };
return {
velocity(newExtent.x - oldExtent.x, scaleX),
velocity(newRight - oldRight, scaleX),
velocity(oldBottom - newBottom, scaleY),
velocity(newExtent.y - oldExtent.y, scaleY),
};
}
+115
View File
@@ -0,0 +1,115 @@
#pragma once
#include "Kawase.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
#include <array>
#include <limits>
#include <vector>
namespace Render::GL {
class CGLFramebuffer;
struct SFluidJarGeometryTransform {
Vector2D positionScale = {1, 1};
Vector2D positionOffset = {};
Vector2D velocityScale = {1, 1};
};
struct SFluidJarOutputTransform {
Vector2D xAxis = {1, 0};
Vector2D yAxis = {0, 1};
Vector2D offset = {};
};
class CFluidJarBlurMaterial final : public IGLBlurMaterial {
public:
explicit CFluidJarBlurMaterial(CHyprOpenGLImpl& impl);
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
int64_t blurSizeForDamage(int64_t size) const override;
float sampleRadius() const override;
void prepare(const SBlurMaterialContext& context) override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
struct SState {
PHLWINDOWREF window;
std::array<SP<CGLFramebuffer>, 2> particles;
std::array<SP<CGLFramebuffer>, 2> graph;
std::array<SP<CGLFramebuffer>, 2> tracking;
std::array<SP<CGLFramebuffer>, 2> visual;
Vector2D simulationSize = {};
Vector2D particleTextureSize = {};
Vector2D graphTextureSize = {};
Vector2D gridSize = {};
CBox extent = {};
Time::steady_tp lastUpdate = {};
std::array<float, 4> wallVelocities = {};
double accumulator = 0.0;
double animationTime = 0.0;
float fillAmount = 0.F;
float precision = 1.F;
int particleCount = 0;
uint64_t simulationFrame = 0;
uint64_t lastFrame = std::numeric_limits<uint64_t>::max();
uint8_t currentParticles = 0;
uint8_t currentGraph = 0;
uint8_t currentTracking = 0;
uint8_t currentVisual = 0;
bool hasExtent = false;
};
SState* stateForContext(const SBlurContext& context, bool create);
const SState* stateForContext(const SBlurContext& context) const;
void updateState(SState& state, const CBox& extent);
void initializeState(SState& state, const Vector2D& simulationSize, float fillAmount, float precision);
void transformState(SState& state, const Vector2D& simulationSize, const SFluidJarGeometryTransform& transform, const std::array<float, 4>& wallVelocities);
void allocateBuffers(std::array<SP<CGLFramebuffer>, 2>& buffers, const Vector2D& size, const std::string& name, DRMFormat format = DRM_FORMAT_ABGR16161616F) const;
void clearBuffers(const std::array<SP<CGLFramebuffer>, 2>& buffers, const std::array<float, 4>& color) const;
void clearIntegerBuffers(const std::array<SP<CGLFramebuffer>, 2>& buffers) const;
void drawInitialize(const SState& state, SP<CGLFramebuffer> target) const;
void drawResample(const SState& state, SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldGridSize, int oldParticleCount,
const SFluidJarGeometryTransform& transform, const std::array<float, 4>& wallVelocities) const;
void drawHistoryResample(SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldSize, const SFluidJarGeometryTransform& transform,
const std::array<float, 4>& fallback, bool linear) const;
void drawParticleStep(SState& state, float dt) const;
void drawGraphStep(SState& state) const;
void drawTrackingStep(SState& state) const;
void drawTrackingResample(SP<CGLFramebuffer> source, SP<CGLFramebuffer> target, const Vector2D& oldSize, const SFluidJarGeometryTransform& transform) const;
void drawVisualStep(SState& state, int steps = 1) const;
void preparePass(SP<CGLFramebuffer> target, const Vector2D& size, WP<CShader> shader) const;
CBox transformedPatternBox(const SBlurContext& context) const;
void scheduleNextFrame(const SState& state) const;
void pruneStates();
CHyprOpenGLImpl& m_impl;
std::vector<SState> m_states;
uint64_t m_frame = 0;
bool m_supported = false;
struct {
CHyprSignalListener renderPre;
CHyprSignalListener windowDestroy;
} m_listeners;
};
class CFluidJarBlurProvider final : public CDualKawaseBlurProvider {
public:
explicit CFluidJarBlurProvider(CHyprOpenGLImpl& impl);
};
Vector2D fluidJarSimulationSize(const Vector2D& extent, float precision = 1.F);
Vector2D fluidJarGridSize(const Vector2D& simulationSize);
int fluidJarParticleCapacity(const Vector2D& simulationSize);
int fluidJarInitialParticleCount(const Vector2D& simulationSize, float fillAmount);
int fluidJarResizedParticleCount(int oldParticleCount, const Vector2D& simulationSize);
float fluidJarDamageRadius(int64_t size, int64_t passes, float distortion = 1.F);
SFluidJarOutputTransform fluidJarOutputTransform(eTransform transform);
SFluidJarGeometryTransform fluidJarGeometryTransform(const CBox& oldExtent, const CBox& newExtent, const Vector2D& oldSimulationSize, const Vector2D& newSimulationSize,
bool preserveWorldPosition = true);
std::array<float, 4> fluidJarWallVelocities(const CBox& oldExtent, const CBox& newExtent, const Vector2D& simulationSize, float elapsed, float speed = 1.F);
}
+68
View File
@@ -0,0 +1,68 @@
#include "Glass.hpp"
#include "Kawase.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include <algorithm>
#include <cmath>
#include <utility>
using namespace Render;
using namespace Render::GL;
static constexpr float MAX_GLASS_REFRACTION = 20.F;
CGlassBlurMaterial::CGlassBlurMaterial(eBlurType type, ePreparedFragmentShader finishFragment, bool preparedInput) :
m_type(type), m_finishFragment(finishFragment), m_preparedInput(preparedInput) {
;
}
CGlassBlurProvider::CGlassBlurProvider(CHyprOpenGLImpl& impl, eBlurType type, ePreparedFragmentShader finishFragment) :
CDualKawaseBlurProvider(impl, makeUnique<CGlassBlurMaterial>(type, finishFragment)) {
;
}
CGlassBlurProvider::CGlassBlurProvider(CHyprOpenGLImpl& impl, UP<IGLBlurMaterial> material) : CDualKawaseBlurProvider(impl, std::move(material)) {
;
}
eBlurType CGlassBlurMaterial::type() const noexcept {
return m_type;
}
SBlurMaterialRequirements CGlassBlurMaterial::requirements() const noexcept {
return {
.finishFragment = m_finishFragment,
.preparedInput = m_preparedInput,
};
}
void CGlassBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
static auto PGLASSREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:glass:refraction");
static auto PGLASSSIZE = CConfigValue<Config::FLOAT>("decoration:blur:glass:size");
static auto PGLASSROUGHNESS = CConfigValue<Config::FLOAT>("decoration:blur:glass:roughness");
const auto clampedStrength = std::clamp(context.strength, 0.F, 1.F);
shader->setUniformFloat(SHADER_GLASS_REFRACTION, std::clamp(*PGLASSREFRACTION, 0.F, MAX_GLASS_REFRACTION) * clampedStrength);
shader->setUniformFloat(SHADER_GLASS_SIZE, std::clamp(*PGLASSSIZE, 4.F, 512.F));
shader->setUniformFloat(SHADER_GLASS_ROUGHNESS, std::clamp(*PGLASSROUGHNESS, 0.F, 1.F) * clampedStrength);
const CBox patternBox = context.blurContext.patternBox.value_or(CBox{});
shader->setUniformFloat2(SHADER_GLASS_POSITION, sc<float>(patternBox.x), sc<float>(patternBox.y));
}
float CGlassBlurMaterial::sampleRadius() const {
static auto PGLASSREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:glass:refraction");
return std::ceil(std::clamp(*PGLASSREFRACTION, 0.F, MAX_GLASS_REFRACTION));
}
float Render::GL::glassDamageRadius(int64_t size, int64_t passes, float refraction) {
return dualKawaseDamageRadius(size, passes) + std::ceil(std::clamp(refraction, 0.F, MAX_GLASS_REFRACTION));
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Kawase.hpp"
namespace Render::GL {
class CGlassBlurMaterial : public IGLBlurMaterial {
public:
CGlassBlurMaterial(eBlurType type, ePreparedFragmentShader finishFragment, bool preparedInput = false);
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
float sampleRadius() const override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
const eBlurType m_type;
const ePreparedFragmentShader m_finishFragment;
const bool m_preparedInput;
};
class CGlassBlurProvider : public CDualKawaseBlurProvider {
public:
CGlassBlurProvider(CHyprOpenGLImpl& impl, eBlurType type, ePreparedFragmentShader finishFragment);
protected:
CGlassBlurProvider(CHyprOpenGLImpl& impl, UP<IGLBlurMaterial> material);
};
float glassDamageRadius(int64_t size, int64_t passes, float refraction);
}
+40
View File
@@ -0,0 +1,40 @@
#include "Haze.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../helpers/cm/ColorManagement.hpp"
#include <algorithm>
using namespace Render;
using namespace Render::GL;
using namespace NColorManagement;
eBlurType CHazeBlurMaterial::type() const noexcept {
return eBlurType::BLUR_HAZE;
}
SBlurMaterialRequirements CHazeBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_HAZEFINISH,
};
}
int64_t CHazeBlurMaterial::blurSizeForDamage(int64_t size) const {
return std::clamp<int64_t>(size, 1, 40);
}
void CHazeBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
static auto PHAZEINTENSITY = CConfigValue<Config::FLOAT>("decoration:blur:haze:intensity");
static auto PHAZEIRIDESCENCE = CConfigValue<Config::FLOAT>("decoration:blur:haze:iridescence");
shader->setUniformFloat(SHADER_HAZE_INTENSITY, std::clamp(*PHAZEINTENSITY, 0.F, 1.F) * std::clamp(context.strength, 0.F, 1.F));
shader->setUniformFloat(SHADER_HAZE_IRIDESCENCE, std::clamp(*PHAZEIRIDESCENCE, 0.F, 1.F));
shader->setUniformInt(SHADER_HAZE_TRANSFER_FUNCTION, sc<int>(getDefaultImageDescription()->value().transferFunction));
}
CHazeBlurProvider::CHazeBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CHazeBlurMaterial>()) {
;
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "Kawase.hpp"
namespace Render::GL {
class CHazeBlurMaterial final : public IGLBlurMaterial {
public:
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
int64_t blurSizeForDamage(int64_t size) const override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
};
class CHazeBlurProvider final : public CDualKawaseBlurProvider {
public:
explicit CHazeBlurProvider(CHyprOpenGLImpl& impl);
};
}
+60
View File
@@ -0,0 +1,60 @@
#include "HeatShimmer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../event/EventBus.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
using namespace Render;
using namespace Render::GL;
static constexpr float MAX_HEAT_SHIMMER_SPEED = 10.F;
static constexpr double HEAT_SHIMMER_BASE_SPEED = 0.8;
static constexpr double HEAT_SHIMMER_PERIOD = 6.283185307179586;
static float heatShimmerSpeed() {
static auto PHEATSHIMMERSPEED = CConfigValue<Config::FLOAT>("decoration:blur:heat_shimmer:speed");
return std::clamp(*PHEATSHIMMERSPEED, 0.F, MAX_HEAT_SHIMMER_SPEED);
}
CHeatShimmerBlurMaterial::CHeatShimmerBlurMaterial() : CGlassBlurMaterial(eBlurType::BLUR_HEAT_SHIMMER, SH_FRAG_HEATSHIMMERFINISH), m_lastAnimationUpdate(Time::steadyNow()) {
m_configListener = Event::bus()->m_events.config.props_refreshed.listen([this](const bool) { updateAnimation(heatShimmerSpeed()); });
}
CHeatShimmerBlurProvider::CHeatShimmerBlurProvider(CHyprOpenGLImpl& impl) : CGlassBlurProvider(impl, makeUnique<CHeatShimmerBlurMaterial>()) {
;
}
bool CHeatShimmerBlurMaterial::isAnimated() const noexcept {
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
static auto PGLASSREFRACTION = CConfigValue<Config::FLOAT>("decoration:blur:glass:refraction");
static auto PGLASSROUGHNESS = CConfigValue<Config::FLOAT>("decoration:blur:glass:roughness");
const auto SPEED = heatShimmerSpeed();
updateAnimation(SPEED);
return *PBLURENABLED && SPEED > 0.F && (*PGLASSREFRACTION > 0.F || *PGLASSROUGHNESS > 0.F);
}
void CHeatShimmerBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
CGlassBlurMaterial::bindFinish(shader, context);
updateAnimation(heatShimmerSpeed());
shader->setUniformFloat(SHADER_TIME, animationPhase());
}
void CHeatShimmerBlurMaterial::updateAnimation(float speed) const {
const auto NOW = Time::steadyNow();
m_animationTime += std::chrono::duration<double>(NOW - m_lastAnimationUpdate).count() * m_previousSpeed;
m_lastAnimationUpdate = NOW;
m_previousSpeed = speed;
}
float CHeatShimmerBlurMaterial::animationPhase() const {
if (m_previousSpeed <= 0.F)
return 0.F;
return sc<float>(std::fmod(m_animationTime * HEAT_SHIMMER_BASE_SPEED, HEAT_SHIMMER_PERIOD));
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Glass.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
namespace Render::GL {
class CHeatShimmerBlurMaterial final : public CGlassBlurMaterial {
public:
CHeatShimmerBlurMaterial();
bool isAnimated() const noexcept override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
void updateAnimation(float speed) const;
float animationPhase() const;
mutable Time::steady_tp m_lastAnimationUpdate;
mutable double m_animationTime = 0.0;
mutable float m_previousSpeed = 0.F;
CHyprSignalListener m_configListener;
};
class CHeatShimmerBlurProvider final : public CGlassBlurProvider {
public:
explicit CHeatShimmerBlurProvider(CHyprOpenGLImpl& impl);
};
}
+292
View File
@@ -0,0 +1,292 @@
#include "Kawase.hpp"
#include "../../OpenGL.hpp"
#include "../../Renderer.hpp"
#include "../../../config/ConfigValue.hpp"
#include <algorithm>
#include <utility>
using namespace Render;
using namespace Render::GL;
using namespace NColorManagement;
static SCMSettings blurIntermediateCMSettings(bool toIntermediate) {
const auto WORKBUFFER = g_pHyprRenderer->workBufferImageDescription();
const auto INTERMEDIATE = getDefaultImageDescription();
auto settings = toIntermediate ? g_pHyprRenderer->getCMSettings(WORKBUFFER, INTERMEDIATE) : g_pHyprRenderer->getCMSettings(INTERMEDIATE, WORKBUFFER);
auto& range = toIntermediate ? settings.dstTFRange : settings.srcTFRange;
range.max = std::max(range.max, sc<float>(WORKBUFFER->value().luminances.max));
return settings;
}
CDualKawaseBlurProvider::CDualKawaseBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CDefaultBlurMaterial>()) {
;
}
CDualKawaseBlurProvider::CDualKawaseBlurProvider(CHyprOpenGLImpl& impl, UP<IGLBlurMaterial> material) : m_impl(impl), m_material(std::move(material)) {
RASSERT(m_material, "Cannot create a dual Kawase blur provider without a material");
;
}
eBlurType CDualKawaseBlurProvider::type() const noexcept {
return m_material->type();
}
bool CDualKawaseBlurProvider::isAnimated() const noexcept {
return m_material->isAnimated();
}
bool CDualKawaseBlurProvider::requiresLiveBlur() const noexcept {
return m_material->requirements().liveBlur;
}
float Render::GL::dualKawaseDamageRadius(int64_t size, int64_t passes) {
const auto blurPasses = std::clamp(passes, sc<int64_t>(1), sc<int64_t>(8));
const auto accumulatedScale = (1 << blurPasses) - 1;
return 2.F * std::max(size, sc<int64_t>(1)) * accumulatedScale;
}
void CDualKawaseBlurProvider::expandDamage(CRegion& damage, float multiplier) const {
damage.expand(damageRadius() * multiplier);
}
float CDualKawaseBlurProvider::damageRadius() const {
static auto PBLURSIZE = CConfigValue<Config::INTEGER>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Config::INTEGER>("decoration:blur:passes");
return dualKawaseDamageRadius(m_material->blurSizeForDamage(*PBLURSIZE), *PBLURPASSES) + m_material->sampleRadius();
}
SP<CGLFramebuffer> CDualKawaseBlurProvider::blurGL(SP<CGLFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context) {
TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage");
auto& m_renderData = g_pHyprRenderer->m_renderData;
const auto BLENDBEFORE = m_impl.m_blend;
m_impl.blend(false);
m_impl.setCapStatus(GL_STENCIL_TEST, false);
CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX);
static auto PBLURSIZE = CConfigValue<Config::INTEGER>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Config::INTEGER>("decoration:blur:passes");
static auto PBLURVIBRANCY = CConfigValue<Config::FLOAT>("decoration:blur:vibrancy");
static auto PBLURVIBRANCYDARKNESS = CConfigValue<Config::FLOAT>("decoration:blur:vibrancy_darkness");
const auto BLUR_PASSES = std::clamp(*PBLURPASSES, sc<int64_t>(1), sc<int64_t>(8));
CRegion outputDamage{originalDamage};
const SBlurMaterialContext materialContext{
.blurContext = context,
.outputDamage = outputDamage,
.strength = strength,
};
m_material->prepare(materialContext);
CRegion workingDamage{outputDamage};
expandDamage(workingDamage);
const auto MATERIAL_REQUIREMENTS = m_material->requirements();
const bool REQUIRES_PREPARED_INPUT = MATERIAL_REQUIREMENTS.preparedInput;
const auto PMIRRORFB = dynamicPointerCast<CGLFramebuffer>(m_renderData.pMonitor->resources()->getUnusedWorkBuffer());
const auto PMIRRORSWAPFB = dynamicPointerCast<CGLFramebuffer>(m_renderData.pMonitor->resources()->getUnusedWorkBuffer());
RASSERT(PMIRRORFB && PMIRRORSWAPFB, "Failed to obtain GL work buffers for dual Kawase blur");
const auto PPREPAREDFB = REQUIRES_PREPARED_INPUT ? dynamicPointerCast<CGLFramebuffer>(m_renderData.pMonitor->resources()->getUnusedWorkBuffer()) : PMIRRORSWAPFB;
RASSERT(PPREPAREDFB, "Failed to obtain GL prepared work buffer for dual Kawase blur");
auto currentRenderToFB = PMIRRORFB;
// Begin with base color adjustments - global brightness and contrast
// TODO: make this a part of the first pass maybe to save on a drawcall?
{
static auto PBLURCONTRAST = CConfigValue<Config::FLOAT>("decoration:blur:contrast");
static auto PBLURBRIGHTNESS = CConfigValue<Config::FLOAT>("decoration:blur:brightness");
static auto PBLEND = CConfigValue<Config::INTEGER>("render:use_shader_blur_blend");
PPREPAREDFB->bind();
PPREPAREDFB->clearAfterInvalidation();
m_impl.setActiveTexture(GL_TEXTURE0);
auto currentTex = source->getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
WP<CShader> shader;
const bool skipCM = !m_impl.m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription());
if (!skipCM) {
const auto settings = blurIntermediateCMSettings(/* toIntermediate */ true);
shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_BLURPREPARE, SH_FEAT_CM, settings.sourceTF, settings.targetTF));
m_impl.passCMUniforms(shader, g_pHyprRenderer->workBufferImageDescription(), getDefaultImageDescription(), false, -1.F, -1, settings);
shader->setUniformFloat(SHADER_SDR_SATURATION,
m_renderData.pMonitor->m_sdrSaturation > 0 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrSaturation :
1.0f);
shader->setUniformFloat(SHADER_SDR_BRIGHTNESS,
m_renderData.pMonitor->m_sdrBrightness > 0 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
} else
shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_BLURPREPARE));
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST);
shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);
shader->setUniformInt(SHADER_TEX, 0);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!workingDamage.empty()) {
workingDamage.forEachRect([this](const auto& RECT) {
m_impl.scissor(&RECT, false);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
currentRenderToFB = PPREPAREDFB;
}
auto drawPass = [&](WP<CShader> shader, ePreparedFragmentShader frag, CRegion* passDamage) {
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
m_impl.setActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * strength);
if (frag == SH_FRAG_BLUR1) {
shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_transformedSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_transformedSize.y / 2.f));
shader->setUniformInt(SHADER_PASSES, BLUR_PASSES);
shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY);
shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS);
} else
shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_transformedSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_transformedSize.y * 2.f));
shader->setUniformInt(SHADER_TEX, 0);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!passDamage->empty()) {
passDamage->forEachRect([this](const auto& RECT) {
m_impl.scissor(&RECT, false);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
};
PMIRRORFB->bind();
PMIRRORFB->clearAfterInvalidation();
PMIRRORSWAPFB->getTexture()->bind();
CRegion tempDamage{workingDamage};
auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_BLUR1));
for (auto i = 1; i <= BLUR_PASSES; ++i) {
tempDamage = workingDamage.copy().scale(1.f / (1 << i));
drawPass(shader, SH_FRAG_BLUR1, &tempDamage);
}
shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_BLUR2));
for (auto i = BLUR_PASSES - 1; i >= 0; --i) {
tempDamage = workingDamage.copy().scale(1.f / (1 << i));
drawPass(shader, SH_FRAG_BLUR2, &tempDamage);
}
{
static auto PBLURNOISE = CConfigValue<Config::FLOAT>("decoration:blur:noise");
static auto PBLURBRIGHTNESS = CConfigValue<Config::FLOAT>("decoration:blur:brightness");
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
m_impl.setActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
currentTex->bind();
currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (REQUIRES_PREPARED_INPUT) {
m_impl.setActiveTexture(GL_TEXTURE1);
auto preparedTex = PPREPAREDFB->getTexture();
preparedTex->bind();
preparedTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_impl.setActiveTexture(GL_TEXTURE0);
}
const bool skipCM = !m_impl.m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription());
if (!skipCM) {
const auto settings = blurIntermediateCMSettings(/* toIntermediate */ false);
shader = m_impl.useShader(m_impl.getShaderVariant(MATERIAL_REQUIREMENTS.finishFragment, SH_FEAT_CM, settings.sourceTF, settings.targetTF));
m_impl.passCMUniforms(shader, getDefaultImageDescription(), g_pHyprRenderer->workBufferImageDescription(), false, -1.F, -1, settings);
shader->setUniformFloat(SHADER_SDR_SATURATION,
m_renderData.pMonitor->m_sdrSaturation > 0 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrSaturation :
1.0f);
shader->setUniformFloat(SHADER_SDR_BRIGHTNESS,
m_renderData.pMonitor->m_sdrBrightness > 0 &&
g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
} else
shader = m_impl.useShader(m_impl.getShaderVariant(MATERIAL_REQUIREMENTS.finishFragment));
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE);
shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS);
shader->setUniformInt(SHADER_TEX, 0);
if (REQUIRES_PREPARED_INPUT)
shader->setUniformInt(SHADER_SHARP_TEX, 1);
m_material->bindFinish(shader, materialContext);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
if (!outputDamage.empty()) {
outputDamage.forEachRect([this](const auto& RECT) {
m_impl.scissor(&RECT, false /* this region is already transformed */);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
});
}
glBindVertexArray(0);
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
}
PMIRRORFB->getTexture()->unbind();
m_impl.blend(BLENDBEFORE);
return currentRenderToFB;
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Material.hpp"
#include "Provider.hpp"
namespace Render::GL {
class CHyprOpenGLImpl;
class CDualKawaseBlurProvider : public IGLBlurProvider {
public:
explicit CDualKawaseBlurProvider(CHyprOpenGLImpl& impl);
CDualKawaseBlurProvider(CHyprOpenGLImpl& impl, UP<IGLBlurMaterial> material);
eBlurType type() const noexcept override;
bool isAnimated() const noexcept override;
bool requiresLiveBlur() const noexcept override;
void expandDamage(CRegion& damage, float multiplier = 1.F) const override;
protected:
SP<CGLFramebuffer> blurGL(SP<CGLFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context) override;
private:
float damageRadius() const;
CHyprOpenGLImpl& m_impl;
UP<IGLBlurMaterial> m_material;
};
float dualKawaseDamageRadius(int64_t size, int64_t passes);
}
+42
View File
@@ -0,0 +1,42 @@
#include "Material.hpp"
#include "../../ShaderLoader.hpp"
#include <algorithm>
using namespace Render;
using namespace Render::GL;
bool IGLBlurMaterial::isAnimated() const noexcept {
return false;
}
int64_t IGLBlurMaterial::blurSizeForDamage(int64_t size) const {
return size;
}
float IGLBlurMaterial::sampleRadius() const {
return 0.F;
}
void IGLBlurMaterial::prepare(const SBlurMaterialContext&) {
;
}
void IGLBlurMaterial::bindFinish(WP<CShader>, const SBlurMaterialContext&) const {
;
}
eBlurType CDefaultBlurMaterial::type() const noexcept {
return eBlurType::BLUR_DUAL_KAWASE;
}
SBlurMaterialRequirements CDefaultBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_BLURFINISH,
};
}
int64_t CDefaultBlurMaterial::blurSizeForDamage(int64_t size) const {
return std::clamp<int64_t>(size, 1, 40);
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "../../ShaderLoader.hpp"
#include "../../blur/Provider.hpp"
class CShader;
namespace Render::GL {
class CHyprOpenGLImpl;
struct SBlurMaterialRequirements {
ePreparedFragmentShader finishFragment = SH_FRAG_BLURFINISH;
bool preparedInput = false;
bool liveBlur = false;
};
struct SBlurMaterialContext {
const SBlurContext& blurContext;
const CRegion& outputDamage;
float strength = 1.F;
};
class IGLBlurMaterial {
public:
virtual ~IGLBlurMaterial() = default;
virtual eBlurType type() const noexcept = 0;
virtual SBlurMaterialRequirements requirements() const noexcept = 0;
virtual bool isAnimated() const noexcept;
virtual int64_t blurSizeForDamage(int64_t size) const;
virtual float sampleRadius() const;
virtual void prepare(const SBlurMaterialContext& context);
virtual void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const;
protected:
IGLBlurMaterial() = default;
};
class CDefaultBlurMaterial final : public IGLBlurMaterial {
public:
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
int64_t blurSizeForDamage(int64_t size) const override;
};
}
+14
View File
@@ -0,0 +1,14 @@
#include "Prism.hpp"
#include "../../ShaderLoader.hpp"
using namespace Render;
using namespace Render::GL;
CPrismBlurMaterial::CPrismBlurMaterial() : CGlassBlurMaterial(eBlurType::BLUR_PRISM, SH_FRAG_PRISMFINISH, true) {
;
}
CPrismBlurProvider::CPrismBlurProvider(CHyprOpenGLImpl& impl) : CGlassBlurProvider(impl, makeUnique<CPrismBlurMaterial>()) {
;
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "Glass.hpp"
namespace Render::GL {
class CPrismBlurMaterial final : public CGlassBlurMaterial {
public:
CPrismBlurMaterial();
};
class CPrismBlurProvider final : public CGlassBlurProvider {
public:
explicit CPrismBlurProvider(CHyprOpenGLImpl& impl);
};
}
+13
View File
@@ -0,0 +1,13 @@
#include "Provider.hpp"
#include "../GLFramebuffer.hpp"
#include "../../../debug/log/Logger.hpp"
using namespace Render;
using namespace Render::GL;
SP<IFramebuffer> IGLBlurProvider::blur(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context) {
const auto glSource = dynamicPointerCast<CGLFramebuffer>(source);
RASSERT(glSource, "Tried to use a GL blur provider with a non-GL framebuffer");
return blurGL(glSource, strength, originalDamage, context);
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "../../blur/Provider.hpp"
namespace Render::GL {
class CGLFramebuffer;
class IGLBlurProvider : public Render::IBlurProvider {
public:
SP<IFramebuffer> blur(SP<IFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context = {}) final;
protected:
IGLBlurProvider() = default;
virtual SP<CGLFramebuffer> blurGL(SP<CGLFramebuffer> source, float strength, const CRegion& originalDamage, const SBlurContext& context) = 0;
};
}
+194
View File
@@ -0,0 +1,194 @@
#include "Ripple.hpp"
#include "Kawase.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../pointer/PointerManager.hpp"
#include "../../../state/MonitorState.hpp"
#include "../../../managers/input/InputManager.hpp"
#include <algorithm>
#include <cmath>
#include <ranges>
#include <vector>
using namespace Render;
using namespace Render::GL;
static constexpr float MAX_RIPPLE_DISPLACEMENT = 32.F;
CRippleBlurMaterial::CRippleBlurMaterial() {
m_listeners.mouseButton = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent event, Event::SCallbackInfo&) {
m_lastMouseHeldCoord.reset();
if (event.state == WL_POINTER_BUTTON_STATE_PRESSED) {
m_mouseIsHeld = true;
return;
}
if (event.state != WL_POINTER_BUTTON_STATE_RELEASED)
return;
m_mouseIsHeld = false;
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
static auto PRIPPLEDURATION = CConfigValue<Config::FLOAT>("decoration:blur:ripple:duration");
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLURENABLED || *PRIPPLESTRENGTH <= 0.F || *PRIPPLEDURATION <= 0.F)
return;
addImpulse();
});
m_listeners.mouseMotion = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D pos, Event::SCallbackInfo&) {
if (!m_mouseIsHeld)
return;
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
static auto PRIPPLEDURATION = CConfigValue<Config::FLOAT>("decoration:blur:ripple:duration");
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLURENABLED || *PRIPPLESTRENGTH <= 0.F || *PRIPPLEDURATION <= 0.F)
return;
if (m_lastMouseHeldCoord) {
const auto Δ = (*m_lastMouseHeldCoord - g_pInputManager->getMouseCoordsInternal()).size();
if (Δ < 6.9F) // arbitrarily chosen by me, fuck you
return;
}
addImpulse();
m_lastMouseHeldCoord = g_pInputManager->getMouseCoordsInternal();
});
}
CRippleBlurProvider::CRippleBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CRippleBlurMaterial>()) {
;
}
eBlurType CRippleBlurMaterial::type() const noexcept {
return eBlurType::BLUR_RIPPLE;
}
void CRippleBlurMaterial::addImpulse() {
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
static auto PRIPPLERADIUS = CConfigValue<Config::FLOAT>("decoration:blur:ripple:radius");
static auto PRIPPLEWIDTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:width");
static auto PRIPPLEDURATION = CConfigValue<Config::FLOAT>("decoration:blur:ripple:duration");
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
const auto POS = g_pInputManager->getMouseCoordsInternal();
const auto PMONITOR = State::monitorState()->query().vec(POS).run();
if (!PMONITOR)
return;
const auto NOW = Time::steadyNow();
auto& impulse = m_impulses[m_nextImpulse];
const auto AGE = std::chrono::duration<float>(NOW - impulse.started).count();
if (impulse.occupied && AGE >= 0.F && AGE < *PRIPPLEDURATION)
damageImpulse(impulse);
impulse = SImpulse{
.globalPosition = POS,
.started = NOW,
.monitor = PMONITOR,
.damageReach = rippleOutputReach(*PRIPPLERADIUS, *PRIPPLEWIDTH),
.occupied = true,
};
m_nextImpulse = (m_nextImpulse + 1) % MAX_IMPULSES;
damageImpulse(impulse);
}
bool CRippleBlurMaterial::isAnimated() const noexcept {
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
static auto PRIPPLEDURATION = CConfigValue<Config::FLOAT>("decoration:blur:ripple:duration");
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLURENABLED || *PRIPPLESTRENGTH <= 0.F || *PRIPPLEDURATION <= 0.F || !g_pHyprRenderer->m_renderData.pMonitor)
return false;
const auto now = Time::steadyNow();
return std::ranges::any_of(m_impulses, [&](const auto& impulse) { return impulseIsActive(impulse, g_pHyprRenderer->m_renderData.pMonitor, now, *PRIPPLEDURATION); });
}
SBlurMaterialRequirements CRippleBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_RIPPLEFINISH,
};
}
void CRippleBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
static auto PRIPPLERADIUS = CConfigValue<Config::FLOAT>("decoration:blur:ripple:radius");
static auto PRIPPLEWIDTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:width");
static auto PRIPPLEDURATION = CConfigValue<Config::FLOAT>("decoration:blur:ripple:duration");
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
const auto now = Time::steadyNow();
const auto duration = std::max(*PRIPPLEDURATION, 0.001F);
std::vector<float> impulses;
impulses.reserve(MAX_IMPULSES * 4);
for (const auto& impulse : m_impulses) {
if (!impulseIsActive(impulse, monitor, now, duration))
continue;
const auto position = (impulse.globalPosition - monitor->m_position) * monitor->m_scale;
const auto age = std::chrono::duration<float>(now - impulse.started).count();
impulses.insert(impulses.end(), {sc<float>(position.x), sc<float>(position.y), age, 0.F});
}
const auto count = sc<GLsizei>(impulses.size() / 4);
shader->setUniformInt(SHADER_RIPPLE_COUNT, count);
if (count > 0)
shader->setUniform4fv(SHADER_RIPPLE_IMPULSES, count, impulses);
shader->setUniformFloat4(SHADER_RIPPLE_PARAMS, duration, std::max(*PRIPPLERADIUS, 1.F), std::max(*PRIPPLEWIDTH, 1.F),
std::clamp(*PRIPPLESTRENGTH, 0.F, MAX_RIPPLE_DISPLACEMENT) * std::clamp(context.strength, 0.F, 1.F));
}
float CRippleBlurMaterial::sampleRadius() const {
static auto PRIPPLESTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:ripple:strength");
return std::ceil(std::clamp(*PRIPPLESTRENGTH, 0.F, MAX_RIPPLE_DISPLACEMENT));
}
void CRippleBlurMaterial::damageImpulse(const SImpulse& impulse) const {
const auto monitor = impulse.monitor.lock();
if (!monitor)
return;
const auto local = (impulse.globalPosition - monitor->m_position) * monitor->m_scale;
const auto left = std::floor(local.x - impulse.damageReach);
const auto top = std::floor(local.y - impulse.damageReach);
const auto right = std::ceil(local.x + impulse.damageReach);
const auto bottom = std::ceil(local.y + impulse.damageReach);
monitor->m_blurFBDirty = true;
monitor->addDamage(CBox{left, top, right - left, bottom - top});
}
bool CRippleBlurMaterial::impulseIsActive(const SImpulse& impulse, PHLMONITORREF monitor, const Time::steady_tp& now, float duration) const {
if (!impulse.occupied || !monitor || impulse.monitor != monitor)
return false;
const auto age = std::chrono::duration<float>(now - impulse.started).count();
return age >= 0.F && age < duration;
}
float Render::GL::rippleDamageRadius(int64_t size, int64_t passes, float displacement) {
return dualKawaseDamageRadius(size, passes) + std::ceil(std::clamp(displacement, 0.F, MAX_RIPPLE_DISPLACEMENT));
}
float Render::GL::rippleOutputReach(float radius, float width) {
return std::ceil(std::max(radius, 0.F) + std::max(width, 0.F));
}
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include "Kawase.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
#include <array>
#include <optional>
namespace Render::GL {
class CRippleBlurMaterial final : public IGLBlurMaterial {
public:
CRippleBlurMaterial();
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
bool isAnimated() const noexcept override;
float sampleRadius() const override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
static constexpr size_t MAX_IMPULSES = 256;
struct SImpulse {
Vector2D globalPosition = {};
Time::steady_tp started = {};
PHLMONITORREF monitor;
float damageReach = 0.F;
bool occupied = false;
};
void damageImpulse(const SImpulse& impulse) const;
bool impulseIsActive(const SImpulse& impulse, PHLMONITORREF monitor, const Time::steady_tp& now, float duration) const;
void addImpulse();
std::array<SImpulse, MAX_IMPULSES> m_impulses;
size_t m_nextImpulse = 0;
bool m_mouseIsHeld = false;
std::optional<Vector2D> m_lastMouseHeldCoord;
struct {
CHyprSignalListener mouseButton;
CHyprSignalListener mouseMotion;
} m_listeners;
};
class CRippleBlurProvider final : public CDualKawaseBlurProvider {
public:
explicit CRippleBlurProvider(CHyprOpenGLImpl& impl);
};
float rippleDamageRadius(int64_t size, int64_t passes, float displacement);
float rippleOutputReach(float radius, float width);
}
+348
View File
@@ -0,0 +1,348 @@
#include "Water.hpp"
#include "Kawase.hpp"
#include "../GLFramebuffer.hpp"
#include "../../OpenGL.hpp"
#include "../../Renderer.hpp"
#include "../../Shader.hpp"
#include "../../ShaderLoader.hpp"
#include "../../../config/ConfigValue.hpp"
#include "../../../desktop/state/ViewState.hpp"
#include "../../../desktop/view/window/Window.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../managers/input/InputManager.hpp"
#include "../../../pointer/PointerManager.hpp"
#include "../../../state/MonitorState.hpp"
#include <algorithm>
#include <cmath>
#include <drm_fourcc.h>
#include <ranges>
using namespace Render;
using namespace Render::GL;
static constexpr float MAX_WATER_DISPLACEMENT = 32.F;
static constexpr float SIMULATION_SCALE = 0.25F;
static constexpr float MIN_SIMULATION_SIZE = 32.F;
static constexpr float MAX_SIMULATION_SIZE = 512.F;
static constexpr float WATER_FADE_DURATION = 2.F;
static constexpr size_t MAX_STORED_IMPULSES = 64;
CWaterBlurMaterial::CWaterBlurMaterial(CHyprOpenGLImpl& impl) : m_impl(impl) {
m_listeners.mouseButton = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent event, Event::SCallbackInfo&) {
m_lastMouseHeldCoord.reset();
if (event.state == WL_POINTER_BUTTON_STATE_PRESSED) {
m_mouseHeld = true;
addImpulse();
return;
}
if (event.state == WL_POINTER_BUTTON_STATE_RELEASED)
m_mouseHeld = false;
});
m_listeners.mouseMotion = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D position, Event::SCallbackInfo&) {
if (!m_mouseHeld)
return;
if (m_lastMouseHeldCoord && (*m_lastMouseHeldCoord - position).size() < 6.9F)
return;
addImpulse();
m_lastMouseHeldCoord = position;
});
m_listeners.renderPre = Event::bus()->m_events.render.pre.listen([this](PHLMONITOR) { ++m_frame; });
m_listeners.windowDestroy =
Event::bus()->m_events.window.destroy.listen([this](PHLWINDOWREF window) { std::erase_if(m_windowStates, [&](const auto& state) { return state.window == window; }); });
m_listeners.config = Event::bus()->m_events.config.props_refreshed.listen([this](const bool) {
for (auto& state : m_windowStates)
state.reset = true;
for (auto& state : m_monitorStates)
state.reset = true;
});
}
CWaterBlurProvider::CWaterBlurProvider(CHyprOpenGLImpl& impl) : CDualKawaseBlurProvider(impl, makeUnique<CWaterBlurMaterial>(impl)) {
;
}
eBlurType CWaterBlurMaterial::type() const noexcept {
return eBlurType::BLUR_WATER;
}
bool CWaterBlurMaterial::isAnimated() const noexcept {
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
static auto PWATERSTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:water:strength");
if (!*PBLURENABLED || *PWATERSTRENGTH <= 0.F)
return false;
pruneStates();
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
if (!monitor)
return false;
const auto now = Time::steadyNow();
return std::ranges::any_of(m_windowStates, [&](const auto& state) { return state.monitor == monitor && stateIsActive(state, now); }) ||
std::ranges::any_of(m_monitorStates, [&](const auto& state) { return state.monitor == monitor && stateIsActive(state, now); });
}
SBlurMaterialRequirements CWaterBlurMaterial::requirements() const noexcept {
return {
.finishFragment = SH_FRAG_WATERFINISH,
};
}
void CWaterBlurMaterial::prepare(const SBlurMaterialContext& context) {
pruneStates();
const auto state = stateForContext(context.blurContext, false);
if (!state || !g_pHyprRenderer->m_renderData.pMonitor)
return;
const auto extent = transformedPatternBox(context.blurContext);
state->monitor = g_pHyprRenderer->m_renderData.pMonitor;
updateState(*state, extent);
}
void CWaterBlurMaterial::bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const {
static auto PWATERSTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:water:strength");
const auto state = stateForContext(context.blurContext);
if (!state || !state->buffers[state->currentBuffer]) {
shader->setUniformInt(SHADER_WATER_ENABLED, 0);
return;
}
const auto extent = transformedPatternBox(context.blurContext);
if (extent.width <= 0 || extent.height <= 0) {
shader->setUniformInt(SHADER_WATER_ENABLED, 0);
return;
}
glActiveTexture(GL_TEXTURE2);
const auto texture = state->buffers[state->currentBuffer]->getTexture();
texture->bind();
texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glActiveTexture(GL_TEXTURE0);
shader->setUniformInt(SHADER_WATER_ENABLED, 1);
shader->setUniformInt(SHADER_WATER_STATE_TEX, 2);
shader->setUniformFloat2(SHADER_WATER_TEXEL_SIZE, 1.F / state->simulationSize.x, 1.F / state->simulationSize.y);
shader->setUniformFloat4(SHADER_WATER_EXTENT, sc<float>(extent.x), sc<float>(extent.y), sc<float>(extent.width), sc<float>(extent.height));
const auto secondsRemaining = std::chrono::duration<float>(state->activeUntil - Time::steadyNow()).count();
const auto fade = std::clamp(secondsRemaining / WATER_FADE_DURATION, 0.F, 1.F);
shader->setUniformFloat(SHADER_WATER_REFRACTION, std::clamp(*PWATERSTRENGTH, 0.F, MAX_WATER_DISPLACEMENT) * std::clamp(context.strength, 0.F, 1.F) * fade);
}
float CWaterBlurMaterial::sampleRadius() const {
static auto PWATERSTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:water:strength");
return std::ceil(std::clamp(*PWATERSTRENGTH, 0.F, MAX_WATER_DISPLACEMENT));
}
CWaterBlurMaterial::SState* CWaterBlurMaterial::stateForContext(const SBlurContext& context, bool create) {
if (!context.owner.expired())
return windowState(context.owner, create);
return monitorState(g_pHyprRenderer->m_renderData.pMonitor, create);
}
const CWaterBlurMaterial::SState* CWaterBlurMaterial::stateForContext(const SBlurContext& context) const {
if (!context.owner.expired()) {
const auto state = std::ranges::find_if(m_windowStates, [&](const auto& candidate) { return candidate.window == context.owner; });
return state != m_windowStates.end() ? &*state : nullptr;
}
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
const auto state = std::ranges::find_if(m_monitorStates, [&](const auto& candidate) { return candidate.monitor == monitor; });
return state != m_monitorStates.end() ? &*state : nullptr;
}
CWaterBlurMaterial::SState* CWaterBlurMaterial::windowState(PHLWINDOWREF window, bool create) {
const auto state = std::ranges::find_if(m_windowStates, [&](const auto& candidate) { return candidate.window == window; });
if (state != m_windowStates.end())
return &*state;
if (!create)
return nullptr;
return &m_windowStates.emplace_back(SState{.window = window});
}
CWaterBlurMaterial::SState* CWaterBlurMaterial::monitorState(PHLMONITORREF monitor, bool create) {
const auto state = std::ranges::find_if(m_monitorStates, [&](const auto& candidate) { return candidate.monitor == monitor; });
if (state != m_monitorStates.end())
return &*state;
if (!create)
return nullptr;
return &m_monitorStates.emplace_back(SState{.monitor = monitor});
}
void CWaterBlurMaterial::addImpulse() {
static auto PBLURENABLED = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
static auto PWATERSTRENGTH = CConfigValue<Config::FLOAT>("decoration:blur:water:strength");
static auto PWATERRADIUS = CConfigValue<Config::FLOAT>("decoration:blur:water:radius");
if (!*PBLURENABLED || *PWATERSTRENGTH <= 0.F)
return;
const auto position = g_pInputManager->getMouseCoordsInternal();
const auto monitor = State::monitorState()->query().vec(position).run();
if (!monitor)
return;
const auto localPosition = (position - monitor->m_position) * monitor->m_scale;
const auto monitorPosition = Vector2D{localPosition.x / monitor->m_transformedSize.x, localPosition.y / monitor->m_transformedSize.y};
const auto amplitude = std::clamp(*PWATERSTRENGTH / MAX_WATER_DISPLACEMENT, 0.F, 1.F) * 0.5F;
queueImpulse(*monitorState(monitor, true), monitorPosition, *PWATERRADIUS, amplitude);
static auto PBLURSIZE = CConfigValue<Config::INTEGER>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Config::INTEGER>("decoration:blur:passes");
const auto reach = *PWATERRADIUS + waterDamageRadius(*PBLURSIZE, *PBLURPASSES, *PWATERSTRENGTH);
monitor->m_blurFBDirty = true;
monitor->addDamage(CBox{
std::floor(localPosition.x - reach),
std::floor(localPosition.y - reach),
std::ceil(reach * 2.F),
std::ceil(reach * 2.F),
});
const auto window = Desktop::viewState()->hitTest().windowAt(position, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);
if (!window)
return;
const auto box = window->logicalBox();
if (!box || box->width <= 0 || box->height <= 0)
return;
const auto windowPosition = Vector2D{(position.x - box->x) / box->width, (position.y - box->y) / box->height};
const auto state = windowState(window, true);
state->monitor = monitor;
queueImpulse(*state, windowPosition, *PWATERRADIUS, amplitude);
}
void CWaterBlurMaterial::queueImpulse(SState& state, Vector2D position, float radius, float amplitude) {
static auto PWATERDURATION = CConfigValue<Config::FLOAT>("decoration:blur:water:duration");
position.x = std::clamp(position.x, 0.0, 1.0);
position.y = std::clamp(position.y, 0.0, 1.0);
state.impulses.push_back({.position = position, .radius = std::max(radius, 1.F), .amplitude = amplitude});
if (state.impulses.size() > MAX_STORED_IMPULSES)
state.impulses.erase(state.impulses.begin());
const auto duration = std::clamp(*PWATERDURATION, 0.5F, 60.F);
state.activeUntil = Time::steadyNow() + std::chrono::duration_cast<Time::steady_dur>(std::chrono::duration<float>(duration));
}
void CWaterBlurMaterial::updateState(SState& state, const CBox& extent) {
const auto now = Time::steadyNow();
if (!stateIsActive(state, now) || state.lastFrame == m_frame)
return;
const auto simulationSize = Vector2D{
std::clamp(std::ceil(extent.width * SIMULATION_SCALE), sc<double>(MIN_SIMULATION_SIZE), sc<double>(MAX_SIMULATION_SIZE)),
std::clamp(std::ceil(extent.height * SIMULATION_SCALE), sc<double>(MIN_SIMULATION_SIZE), sc<double>(MAX_SIMULATION_SIZE)),
};
if (state.reset || state.simulationSize != simulationSize)
resetState(state, simulationSize);
const auto dt = state.lastUpdate == Time::steady_tp{} ? 1.F / 60.F : std::clamp(sc<float>(std::chrono::duration<float>(now - state.lastUpdate).count()), 0.F, 1.F / 20.F);
drawStateStep(state, dt, extent);
state.lastUpdate = now;
state.lastFrame = m_frame;
}
void CWaterBlurMaterial::resetState(SState& state, const Vector2D& simulationSize) {
for (auto& buffer : state.buffers) {
if (!buffer)
buffer = dynamicPointerCast<CGLFramebuffer>(g_pHyprRenderer->createFB("Water simulation"));
buffer->alloc(sc<int>(simulationSize.x), sc<int>(simulationSize.y), DRM_FORMAT_ARGB8888);
buffer->bind();
g_pHyprRenderer->disableScissor();
glClearColor(0.5F, 0.5F, 0.F, 1.F);
glClear(GL_COLOR_BUFFER_BIT);
}
state.simulationSize = simulationSize;
state.currentBuffer = 0;
state.lastUpdate = {};
state.reset = false;
}
void CWaterBlurMaterial::drawStateStep(SState& state, float dt, const CBox& extent) {
static auto PWATERSPEED = CConfigValue<Config::FLOAT>("decoration:blur:water:speed");
static auto PWATERDAMPING = CConfigValue<Config::FLOAT>("decoration:blur:water:damping");
const auto source = state.buffers[state.currentBuffer];
const auto target = state.buffers[1 - state.currentBuffer];
target->bind();
g_pHyprRenderer->setViewport(0, 0, sc<int>(state.simulationSize.x), sc<int>(state.simulationSize.y));
g_pHyprRenderer->disableScissor();
glActiveTexture(GL_TEXTURE0);
const auto texture = source->getTexture();
texture->bind();
texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
const auto matrix = g_pHyprRenderer->projectBoxToTarget({0, 0, monitor->m_transformedSize.x, monitor->m_transformedSize.y});
const auto shader = m_impl.useShader(m_impl.getShaderVariant(SH_FRAG_WATERSTEP));
shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, matrix.getMatrix());
shader->setUniformInt(SHADER_WATER_STATE_TEX, 0);
shader->setUniformFloat2(SHADER_WATER_TEXEL_SIZE, 1.F / state.simulationSize.x, 1.F / state.simulationSize.y);
shader->setUniformFloat4(SHADER_WATER_PARAMS, dt, std::clamp(*PWATERSPEED, 0.F, 10.F), std::clamp(*PWATERDAMPING, 0.F, 1.F), 0.F);
std::vector<float> impulses;
impulses.reserve(std::min(state.impulses.size(), MAX_IMPULSES) * 4);
const auto scale = std::max(std::min(sc<float>(extent.width), sc<float>(extent.height)), 1.F);
for (const auto& impulse : state.impulses | std::views::take(MAX_IMPULSES))
impulses.insert(impulses.end(), {sc<float>(impulse.position.x), sc<float>(impulse.position.y), impulse.radius / scale, impulse.amplitude});
shader->setUniformInt(SHADER_WATER_IMPULSE_COUNT, sc<int>(impulses.size() / 4));
if (!impulses.empty())
shader->setUniform4fv(SHADER_WATER_IMPULSES, sc<GLsizei>(impulses.size() / 4), impulses);
glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
state.impulses.clear();
state.currentBuffer = 1 - state.currentBuffer;
}
CBox CWaterBlurMaterial::transformedPatternBox(const SBlurContext& context) const {
const auto monitor = g_pHyprRenderer->m_renderData.pMonitor;
if (!monitor)
return {};
return context.patternBox.value_or(CBox{0, 0, monitor->m_transformedSize.x, monitor->m_transformedSize.y});
}
bool CWaterBlurMaterial::stateIsActive(const SState& state, const Time::steady_tp& now) const {
return !state.impulses.empty() || (state.activeUntil != Time::steady_tp{} && now < state.activeUntil);
}
void CWaterBlurMaterial::pruneStates() const {
const auto now = Time::steadyNow();
std::erase_if(m_windowStates, [&](const auto& state) { return state.window.expired() || !state.window->shouldBlur() || !stateIsActive(state, now); });
std::erase_if(m_monitorStates, [&](const auto& state) { return state.monitor.expired() || !stateIsActive(state, now); });
}
float Render::GL::waterDamageRadius(int64_t size, int64_t passes, float displacement) {
return dualKawaseDamageRadius(size, passes) + std::ceil(std::clamp(displacement, 0.F, MAX_WATER_DISPLACEMENT));
}
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include "Kawase.hpp"
#include "../../../helpers/signal/Signal.hpp"
#include "../../../helpers/time/Time.hpp"
#include <vector>
namespace Render::GL {
class CGLFramebuffer;
class CWaterBlurMaterial final : public IGLBlurMaterial {
public:
explicit CWaterBlurMaterial(CHyprOpenGLImpl& impl);
eBlurType type() const noexcept override;
SBlurMaterialRequirements requirements() const noexcept override;
bool isAnimated() const noexcept override;
float sampleRadius() const override;
void prepare(const SBlurMaterialContext& context) override;
void bindFinish(WP<CShader> shader, const SBlurMaterialContext& context) const override;
private:
static constexpr size_t MAX_IMPULSES = 16;
struct SImpulse {
Vector2D position = {};
float radius = 0.F;
float amplitude = 0.F;
};
struct SState {
PHLWINDOWREF window;
PHLMONITORREF monitor;
SP<CGLFramebuffer> buffers[2];
Vector2D simulationSize = {};
std::vector<SImpulse> impulses;
Time::steady_tp lastUpdate = {};
Time::steady_tp activeUntil = {};
uint64_t lastFrame = 0;
uint8_t currentBuffer = 0;
bool reset = true;
};
SState* stateForContext(const SBlurContext& context, bool create);
const SState* stateForContext(const SBlurContext& context) const;
SState* windowState(PHLWINDOWREF window, bool create);
SState* monitorState(PHLMONITORREF monitor, bool create);
void addImpulse();
void queueImpulse(SState& state, Vector2D position, float radius, float amplitude);
void updateState(SState& state, const CBox& extent);
void resetState(SState& state, const Vector2D& simulationSize);
void drawStateStep(SState& state, float dt, const CBox& extent);
CBox transformedPatternBox(const SBlurContext& context) const;
bool stateIsActive(const SState& state, const Time::steady_tp& now) const;
void pruneStates() const;
CHyprOpenGLImpl& m_impl;
mutable std::vector<SState> m_windowStates;
mutable std::vector<SState> m_monitorStates;
uint64_t m_frame = 0;
bool m_mouseHeld = false;
std::optional<Vector2D> m_lastMouseHeldCoord;
struct {
CHyprSignalListener mouseButton;
CHyprSignalListener mouseMotion;
CHyprSignalListener renderPre;
CHyprSignalListener windowDestroy;
CHyprSignalListener config;
} m_listeners;
};
class CWaterBlurProvider final : public CDualKawaseBlurProvider {
public:
explicit CWaterBlurProvider(CHyprOpenGLImpl& impl);
};
float waterDamageRadius(int64_t size, int64_t passes, float displacement);
}
@@ -0,0 +1,72 @@
#include "BackdropScopePassElement.hpp"
#include "../Renderer.hpp"
void CBackdropScopePlanner::begin(SP<SBackdropScope> scope) {
RASSERT(scope, "Cannot plan a null backdrop scope");
scope->required = false;
scope->damage.clear();
m_scopes.emplace_back(std::move(scope));
}
void CBackdropScopePlanner::addLiveBlur(const CRegion& damage) {
if (m_scopes.empty())
return;
m_scopes.back()->required = true;
m_scopes.back()->damage.add(damage);
}
void CBackdropScopePlanner::end(SP<SBackdropScope> scope, const CBox& bounds) {
RASSERT(!m_scopes.empty() && m_scopes.back() == scope, "Unbalanced backdrop scope markers");
if (scope->required)
scope->damage.intersect(bounds);
m_scopes.pop_back();
}
bool CBackdropScopePlanner::empty() const {
return m_scopes.empty();
}
CBackdropScopePassElement::CBackdropScopePassElement(eAction action, SP<SBackdropScope> scope) : m_action(action), m_scope(std::move(scope)) {
;
}
std::vector<UP<IPassElement>> CBackdropScopePassElement::draw() {
if (!m_scope->required)
return {};
if (m_action == eAction::BEGIN)
g_pHyprRenderer->beginBackdropScope(m_scope);
else
g_pHyprRenderer->endBackdropScope(m_scope);
return {};
}
bool CBackdropScopePassElement::needsLiveBlur() {
return false;
}
bool CBackdropScopePassElement::needsPrecomputeBlur() {
return false;
}
bool CBackdropScopePassElement::undiscardable() {
return true;
}
const char* CBackdropScopePassElement::passName() {
return "CBackdropScopePassElement";
}
ePassElementType CBackdropScopePassElement::type() {
return EK_BACKDROP_SCOPE;
}
CBackdropScopePassElement::eAction CBackdropScopePassElement::action() const {
return m_action;
}
SP<SBackdropScope> CBackdropScopePassElement::scope() const {
return m_scope;
}
@@ -0,0 +1,45 @@
#pragma once
#include "PassElement.hpp"
struct SBackdropScope {
bool required = false;
CRegion damage;
};
class CBackdropScopePlanner {
public:
void begin(SP<SBackdropScope> scope);
void addLiveBlur(const CRegion& damage);
void end(SP<SBackdropScope> scope, const CBox& bounds);
bool empty() const;
private:
std::vector<SP<SBackdropScope>> m_scopes;
};
class CBackdropScopePassElement : public IPassElement {
public:
enum class eAction : uint8_t {
BEGIN = 0,
END,
};
CBackdropScopePassElement(eAction action, SP<SBackdropScope> scope);
virtual ~CBackdropScopePassElement() = default;
virtual std::vector<UP<IPassElement>> draw();
virtual bool needsLiveBlur();
virtual bool needsPrecomputeBlur();
virtual bool undiscardable();
virtual const char* passName();
virtual ePassElementType type();
eAction action() const;
SP<SBackdropScope> scope() const;
private:
eAction m_action = eAction::BEGIN;
SP<SBackdropScope> m_scope;
};
+61 -13
View File
@@ -12,6 +12,7 @@
#include "../../protocols/core/Compositor.hpp"
#include "../../state/MonitorState.hpp"
#include "RectPassElement.hpp"
#include "BackdropScopePassElement.hpp"
#include "macros.hpp"
using namespace Render;
@@ -24,6 +25,14 @@ bool CRenderPass::single() const {
return m_passElements.size() == 1;
}
bool CRenderPass::needsLiveBlur() {
return std::ranges::any_of(m_passElements, [](const auto& el) { return el.element->needsLiveBlur(); });
}
bool CRenderPass::needsPrecomputeBlur() {
return std::ranges::any_of(m_passElements, [](const auto& el) { return el.element->needsPrecomputeBlur(); });
}
void CRenderPass::add(UP<IPassElement>&& el) {
m_passElements.emplace_back(SPassElementData{.element = std::move(el)});
}
@@ -104,6 +113,30 @@ void CRenderPass::clear() {
m_passElements.clear();
}
void CRenderPass::planBackdropScopes() {
CBackdropScopePlanner planner;
const CBox bounds = {{}, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize};
for (auto& el : m_passElements) {
if (el.element->type() != EK_BACKDROP_SCOPE) {
if (!el.discard && el.element->needsLiveBlurCached)
planner.addLiveBlur(el.elementDamage);
continue;
}
const auto marker = sc<CBackdropScopePassElement*>(el.element.get());
const auto scope = marker->scope();
RASSERT(scope, "Backdrop scope marker has no scope");
if (marker->action() == CBackdropScopePassElement::eAction::BEGIN)
planner.begin(scope);
else
planner.end(scope, bounds);
}
RASSERT(planner.empty(), "Unclosed backdrop scope marker");
}
CRegion CRenderPass::render(const CRegion& damage_) {
const auto pMonitor = g_pHyprRenderer->m_renderData.pMonitor;
static auto PDEBUGPASS = CConfigValue<Config::INTEGER>("debug:pass");
@@ -156,16 +189,18 @@ CRegion CRenderPass::render(const CRegion& damage_) {
blurRegion.scale(pMonitor->m_scale);
// save a copy for simplify's occlusion test before we mutate for damage expansion
liveBlurRegion = blurRegion.copy().expand(oneBlurRadius() * 2.F);
liveBlurRegion = blurRegion.copy();
g_pHyprRenderer->expandBlurDamage(liveBlurRegion, 2.F);
blurRegion.intersect(m_damage).expand(oneBlurRadius());
blurRegion.intersect(m_damage);
g_pHyprRenderer->expandBlurDamage(blurRegion);
g_pHyprRenderer->m_renderData.finalDamage = blurRegion.copy().add(m_damage);
// FIXME: why does this break on * 1.F ?
// used to work when we expand all the damage... I think? Well, before pass.
// moving a window over blur shows the edges being wonk.
blurRegion.expand(oneBlurRadius() * 1.5F);
g_pHyprRenderer->expandBlurDamage(blurRegion, 1.5F);
m_damage = blurRegion.copy().add(m_damage);
} else
@@ -178,12 +213,18 @@ CRegion CRenderPass::render(const CRegion& damage_) {
} else
simplify(willBlur, liveBlurRegion);
planBackdropScopes();
if (g_pHyprRenderer->m_renderData.pMonitor)
g_pHyprRenderer->m_renderData.pMonitor->m_blurFBShouldRender = willPrecomputeBlur;
if (m_passElements.empty())
return {};
const bool providerIsAnimated = g_pHyprRenderer->blurProviderIsAnimated();
CRegion animatedBlurDamage;
bool usesPrecomputedBlur = false;
for (auto& el : m_passElements) {
if (el.discard) {
el.element->discard();
@@ -192,8 +233,25 @@ CRegion CRenderPass::render(const CRegion& damage_) {
g_pHyprRenderer->m_renderData.damage = el.elementDamage;
g_pHyprRenderer->draw(el.element, el.elementDamage);
if (!providerIsAnimated || (!el.element->needsLiveBlurCached && !el.element->needsPrecomputeBlurCached))
continue;
const auto BB = el.element->boundingBox();
if (!BB)
animatedBlurDamage.add(CBox{{}, pMonitor->m_transformedSize});
else {
auto box = BB->copy().scale(pMonitor->m_scale);
g_pHyprRenderer->m_renderData.renderModif.applyToBox(box);
animatedBlurDamage.add(box);
}
usesPrecomputedBlur = usesPrecomputedBlur || el.element->needsPrecomputeBlurCached;
}
animatedBlurDamage.intersect(CBox{{}, pMonitor->m_transformedSize});
g_pHyprRenderer->scheduleFrameForAnimatedBlur(animatedBlurDamage, usesPrecomputedBlur);
if (*PDEBUGPASS) {
renderDebugData();
g_pEventLoopManager->doLater([] {
@@ -310,16 +368,6 @@ void CRenderPass::renderDebugData() {
m_damage);
}
float CRenderPass::oneBlurRadius() {
// TODO: is this exact range correct?
static auto PBLURSIZE = CConfigValue<Config::INTEGER>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Config::INTEGER>("decoration:blur:passes");
const auto BLUR_PASSES = std::clamp(*PBLURPASSES, sc<int64_t>(1), sc<int64_t>(8));
return std::clamp(*PBLURSIZE, sc<int64_t>(1), sc<int64_t>(40)) * pow(2, BLUR_PASSES); // is this 2^pass? I don't know but it works... I think.
}
void CRenderPass::removeAllOfType(const std::string& type) {
std::erase_if(m_passElements, [&type](const auto& e) { return e.element->passName() == type; });
}
+3 -3
View File
@@ -12,6 +12,8 @@ namespace Render {
public:
bool empty() const;
bool single() const;
bool needsLiveBlur();
bool needsPrecomputeBlur();
void add(UP<IPassElement>&& elem);
void clear();
@@ -33,14 +35,12 @@ namespace Render {
std::vector<SPassElementData> m_passElements;
void simplify(bool willBlur, const CRegion& liveBlurRegion);
float oneBlurRadius();
void planBackdropScopes();
void renderDebugData();
struct {
bool present = false;
SP<ITexture> keyboardFocusText, pointerFocusText, lastWindowText;
} m_debugData;
friend class CHyprOpenGLImpl;
};
}
+1
View File
@@ -18,6 +18,7 @@ enum ePassElementType : uint8_t {
EK_INNER_GLOW,
EK_TRANSFORMED_WINDOW,
EK_CUSTOM,
EK_BACKDROP_SCOPE,
};
class IPassElement {
+2 -2
View File
@@ -6,11 +6,11 @@ CRectPassElement::CRectPassElement(const CRectPassElement::SRectData& data_) : m
}
bool CRectPassElement::needsLiveBlur() {
return m_data.color.a < 1.F && !m_data.xray && m_data.blur;
return m_data.color.a < 1.F && m_data.blur && (!m_data.xray || g_pHyprRenderer->blurProviderRequiresLiveBlur());
}
bool CRectPassElement::needsPrecomputeBlur() {
return m_data.color.a < 1.F && m_data.xray && m_data.blur;
return m_data.color.a < 1.F && m_data.xray && m_data.blur && !g_pHyprRenderer->blurProviderRequiresLiveBlur();
}
std::optional<CBox> CRectPassElement::boundingBox() {
+10 -7
View File
@@ -1,17 +1,20 @@
#pragma once
#include "PassElement.hpp"
#include <hyprutils/math/Region.hpp>
#include <optional>
class CRectPassElement : public IPassElement {
public:
struct SRectData {
CBox box;
CHyprColor color;
int round = 0;
float roundingPower = 2.0f;
bool blur = false, xray = false;
float blurA = 1.F;
CBox clipBox;
CBox box;
CHyprColor color;
int round = 0;
float roundingPower = 2.0f;
bool blur = false, xray = false;
float blurA = 1.F;
std::optional<CBox> blurPatternBox;
PHLWINDOWREF blurOwner;
CBox clipBox;
// internal
CBox modifiedBox;
+16 -2
View File
@@ -18,11 +18,25 @@ CTexPassElement::CTexPassElement(CTexPassElement::SRenderData&& data) : m_data(s
}
bool CTexPassElement::needsLiveBlur() {
return false; // TODO?
return usesLiveBlur();
}
bool CTexPassElement::needsPrecomputeBlur() {
return false; // TODO?
return m_data.blur && !usesLiveBlur();
}
bool CTexPassElement::usesLiveBlur() {
if (m_usesLiveBlur.has_value())
return *m_usesLiveBlur;
if (m_data.liveBlurOverride.has_value()) {
m_usesLiveBlur = m_data.blur && *m_data.liveBlurOverride;
return *m_usesLiveBlur;
}
m_usesLiveBlur =
m_data.blur && (m_data.blockBlurOptimization.value_or(false) || !g_pHyprRenderer->shouldUseNewBlurOptimizations(m_data.currentLS.lock(), m_data.blurOwner.lock()));
return *m_usesLiveBlur;
}
std::optional<CBox> CTexPassElement::boundingBox() {
+9
View File
@@ -45,8 +45,10 @@ class CTexPassElement : public IPassElement {
CBox clipBox;
bool blur = false;
bool forceBlurBlend = false;
std::optional<CBox> blurPatternBox;
std::optional<float> ignoreAlpha;
std::optional<bool> blockBlurOptimization;
std::optional<bool> liveBlurOverride;
bool cmBackToSRGB = false;
bool discardActive = false;
@@ -61,10 +63,12 @@ class CTexPassElement : public IPassElement {
CRegion clipRegion;
PHLLSREF currentLS;
PHLWINDOWREF blurOwner;
SP<Render::ITexture> blurredBG;
SP<Render::ITexture> blurAlphaMatte;
SMotionBlurData motionBlur;
bool blurShapeInvalid = false;
};
CTexPassElement(const SRenderData& data);
@@ -77,6 +81,8 @@ class CTexPassElement : public IPassElement {
virtual CRegion opaqueRegion();
virtual void discard();
bool usesLiveBlur();
virtual const char* passName() {
return "CTexPassElement";
}
@@ -86,4 +92,7 @@ class CTexPassElement : public IPassElement {
};
SRenderData m_data;
private:
std::optional<bool> m_usesLiveBlur;
};
@@ -5,11 +5,11 @@ CTransformedWindowPassElement::CTransformedWindowPassElement(CTransformedWindowP
}
bool CTransformedWindowPassElement::needsLiveBlur() {
return m_data.blur;
return (m_data.blur && m_data.blurUsesLive) || (m_data.pass && m_data.pass->needsLiveBlur());
}
bool CTransformedWindowPassElement::needsPrecomputeBlur() {
return false;
return (m_data.blur && !m_data.blurUsesLive) || (m_data.pass && m_data.pass->needsPrecomputeBlur());
}
std::optional<CBox> CTransformedWindowPassElement::boundingBox() {
@@ -11,6 +11,7 @@ class CTransformedWindowPassElement : public IPassElement {
CBox currentBox;
CBox blurBox;
bool blur = false;
bool blurUsesLive = false;
float blurA = 1.F;
int blurRound = 0;
float blurRoundingPower = 2.F;
+232
View File
@@ -0,0 +1,232 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform sampler2D sharpTex;
uniform int acrylicEnabled;
uniform vec4 acrylicExtent;
uniform float acrylicRadius;
uniform float acrylicRoundingPower;
uniform float acrylicRefraction;
uniform float acrylicBulb;
uniform float acrylicClarity;
uniform float acrylicAberration;
uniform vec4 acrylicTint;
uniform float acrylicStrength;
uniform int acrylicTransferFunction;
uniform float acrylicLuminanceScale;
uniform float noise;
uniform float brightness;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "cm_helpers.glsl"
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
float acrylicLength(vec2 value, float power) {
value = abs(value);
return pow(pow(value.x, power) + pow(value.y, power), 1.0 / power);
}
float roundedBoxSDF(vec2 position, vec2 halfSize, float radius, float power) {
radius = clamp(radius, 0.0, min(halfSize.x, halfSize.y));
vec2 offset = abs(position) - (halfSize - vec2(radius));
vec2 outside = max(offset, vec2(0.0));
float cornerDistance = (outside.x > 0.0 || outside.y > 0.0) ? acrylicLength(outside, power) : 0.0;
return cornerDistance + min(max(offset.x, offset.y), 0.0) - radius;
}
float smootherstep(float edge0, float edge1, float value) {
float progress = clamp((value - edge0) / max(edge1 - edge0, 0.0001), 0.0, 1.0);
return progress * progress * progress * (progress * (progress * 6.0 - 15.0) + 10.0);
}
float nestedRoundedBoxSDF(vec2 position, vec2 halfSize, float radius, float power, float inset) {
vec2 nestedHalfSize = max(halfSize - vec2(inset), vec2(0.001));
float nestedRadius = clamp(radius, 0.0, min(nestedHalfSize.x, nestedHalfSize.y));
return roundedBoxSDF(position, nestedHalfSize, nestedRadius, power);
}
float roundedProfileDepth(vec2 position, vec2 halfSize, float radius, float power, float width, float boundarySdf) {
if (boundarySdf >= 0.0)
return 0.0;
if (nestedRoundedBoxSDF(position, halfSize, radius, power, width) <= 0.0)
return width;
float lower = 0.0;
float upper = width;
for (int i = 0; i < 8; ++i) {
float middle = (lower + upper) * 0.5;
if (nestedRoundedBoxSDF(position, halfSize, radius, power, middle) <= 0.0)
lower = middle;
else
upper = middle;
}
return (lower + upper) * 0.5;
}
vec2 roundedProfileNormal(vec2 position, vec2 halfSize, float radius, float power, float inset) {
vec2 nestedHalfSize = max(halfSize - vec2(inset), vec2(0.001));
float nestedRadius = clamp(radius, 0.0, min(nestedHalfSize.x, nestedHalfSize.y));
vec2 corner = max(abs(position) - (nestedHalfSize - vec2(nestedRadius)), vec2(0.0));
vec2 gradient;
if (corner.x > 0.0001 || corner.y > 0.0001) {
if (power <= 1.0001)
gradient = vec2(corner.x > 0.0001 ? 1.0 : 0.0, corner.y > 0.0001 ? 1.0 : 0.0);
else
gradient = pow(corner, vec2(power - 1.0));
} else {
vec2 edgeDistance = nestedHalfSize - abs(position);
gradient = edgeDistance.x < edgeDistance.y ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
}
gradient *= sign(position);
return gradient / max(length(gradient), 0.0001);
}
float availableTextureTravel(vec2 origin, vec2 direction, vec2 minimumUV, vec2 maximumUV) {
float travel = 1e10;
if (direction.x > 0.000001)
travel = min(travel, (maximumUV.x - origin.x) / direction.x);
else if (direction.x < -0.000001)
travel = min(travel, (minimumUV.x - origin.x) / direction.x);
if (direction.y > 0.000001)
travel = min(travel, (maximumUV.y - origin.y) / direction.y);
else if (direction.y < -0.000001)
travel = min(travel, (minimumUV.y - origin.y) / direction.y);
return max(travel, 0.0);
}
vec4 applyAcrylic(vec4 blurred) {
float effect = clamp(acrylicStrength, 0.0, 1.0);
if (effect <= 0.0)
return blurred;
vec2 halfSize = acrylicExtent.zw * 0.5;
vec2 center = acrylicExtent.xy + halfSize;
vec2 position = gl_FragCoord.xy - center;
float power = clamp(acrylicRoundingPower, 1.0, 10.0);
float minimumHalfSize = min(halfSize.x, halfSize.y);
float boundaryRadius = clamp(acrylicRadius, 0.0, minimumHalfSize);
float maximumBulb = max(minimumHalfSize * 0.8, 1.0);
float bulbWidth = clamp(acrylicBulb, 1.0, maximumBulb);
float boundarySdf = roundedBoxSDF(position, halfSize, boundaryRadius, power);
float antialias = max(fwidth(boundarySdf), 0.75);
float shape = 1.0 - smoothstep(-antialias, antialias, boundarySdf);
vec2 texcoordDx = dFdx(v_texcoord);
vec2 texcoordDy = dFdy(v_texcoord);
if (shape <= 0.0)
return blurred;
float opticalDepth = roundedProfileDepth(position, halfSize, boundaryRadius, power, bulbWidth, boundarySdf);
float progress = clamp(opticalDepth / bulbWidth, 0.0, 1.0);
float clarityCore = smootherstep(0.25, 0.6, progress);
float curvature = 1.0 - smootherstep(0.0, 1.0, progress);
float lensEntry = smootherstep(0.0, 0.08, progress);
float lensExit = 1.0 - smootherstep(0.16, 1.0, progress);
float lens = lensEntry * lensExit;
float outerRim = 1.0 - smootherstep(0.0, max(bulbWidth * 0.07, 2.0), opticalDepth);
float caustic = smootherstep(0.04, 0.13, progress) * (1.0 - smootherstep(0.22, 0.48, progress));
float counterRim = smootherstep(0.18, 0.34, progress) * (1.0 - smootherstep(0.42, 0.72, progress));
vec2 outward = roundedProfileNormal(position, halfSize, boundaryRadius, power, opticalDepth);
vec2 sourceSize = vec2(textureSize(tex, 0));
vec2 halfTexel = 0.5 / sourceSize;
vec2 maximumUV = vec2(1.0) - halfTexel;
vec2 outwardUV = outward.x * texcoordDx + outward.y * texcoordDy;
float maximumRefraction = max(acrylicRefraction, 0.0);
float availableTravel = availableTextureTravel(v_texcoord, outwardUV, halfTexel, maximumUV);
float edgeValidity = smoothstep(0.0, maximumRefraction + 1.0, availableTravel);
float safeRefraction = min(maximumRefraction * edgeValidity, max(availableTravel - 0.5, 0.0));
vec2 displacementPixels = outward * safeRefraction * lens * effect;
vec2 displacementUV = displacementPixels.x * texcoordDx + displacementPixels.y * texcoordDy;
float aberration = clamp(acrylicAberration, 0.0, 0.25);
vec2 redUV = clamp(v_texcoord + displacementUV, halfTexel, maximumUV);
vec2 greenUV = clamp(v_texcoord + displacementUV * (1.0 - aberration * 0.5), halfTexel, maximumUV);
vec2 blueUV = clamp(v_texcoord + displacementUV * (1.0 - aberration), halfTexel, maximumUV);
float outputAlpha = blurred.a;
vec3 blurredLinear = toLinearRGB(blurred.rgb / max(blurred.a, 0.001), acrylicTransferFunction);
vec3 acrylicLinear = blurredLinear;
if (lens > 0.0001 && safeRefraction > 0.0001) {
vec4 displacedBlurred = texture(tex, greenUV);
vec3 displacedBlurredLinear = toLinearRGB(displacedBlurred.rgb / max(displacedBlurred.a, 0.001), acrylicTransferFunction);
acrylicLinear = mix(acrylicLinear, displacedBlurredLinear, effect * curvature * edgeValidity);
}
float fresnelTransmission = mix(1.0, 0.84, curvature);
float clarity = clamp(acrylicClarity, 0.0, 1.0) * effect * fresnelTransmission * clarityCore;
if (clarity > 0.0001) {
vec4 refractedGreen = texture(sharpTex, greenUV);
vec3 refractedLinear = toLinearRGB(refractedGreen.rgb / max(refractedGreen.a, 0.001), acrylicTransferFunction);
if (aberration > 0.0001 && lens > 0.0001 && safeRefraction > 0.0001) {
vec4 refractedRed = texture(sharpTex, redUV);
vec4 refractedBlue = texture(sharpTex, blueUV);
vec3 refractedRedLinear = toLinearRGB(refractedRed.rgb / max(refractedRed.a, 0.001), acrylicTransferFunction);
vec3 refractedBlueLinear = toLinearRGB(refractedBlue.rgb / max(refractedBlue.a, 0.001), acrylicTransferFunction);
refractedLinear.r = refractedRedLinear.r;
refractedLinear.b = refractedBlueLinear.b;
}
acrylicLinear = mix(acrylicLinear, refractedLinear, clarity);
}
vec3 tintLinear = acrylicTint.rgb;
float thickness = mix(0.34, 1.0, curvature);
float transmission = exp(-acrylicTint.a * effect * thickness);
acrylicLinear = acrylicLinear * transmission + tintLinear * (1.0 - transmission);
vec3 surfaceNormal = normalize(vec3(outward * curvature * 2.15, 1.0));
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
vec3 light = normalize(vec3(LIGHT_DIRECTION, 0.72));
vec3 halfway = normalize(light + vec3(0.0, 0.0, 1.0));
float oneMinusNV = 1.0 - max(surfaceNormal.z, 0.0);
float fresnel = 0.0204 + 0.9796 * pow(oneMinusNV, 5.0);
float specular = pow(max(dot(surfaceNormal, halfway), 0.0), 40.0);
float directional = dot(outward, LIGHT_DIRECTION);
float backdropLuma = dot(acrylicLinear, vec3(0.2126, 0.7152, 0.0722));
float normalizedLuma = clamp(backdropLuma / max(acrylicLuminanceScale, 0.001), 0.0, 1.0);
float highlight = outerRim * (0.045 + 0.12 * max(directional, 0.0));
highlight += caustic * (0.035 + 0.08 * max(directional, 0.0));
highlight += curvature * (0.08 * fresnel + 0.13 * specular);
highlight *= effect * mix(1.0, 0.4, normalizedLuma);
float shadow = outerRim * 0.09 * max(-directional, 0.0) + counterRim * (0.025 + 0.05 * max(-directional, 0.0));
shadow *= effect * mix(0.45, 1.0, normalizedLuma);
acrylicLinear *= 1.0 - shadow;
acrylicLinear += mix(vec3(acrylicLuminanceScale), tintLinear, 0.08) * highlight;
vec4 acrylic = fromLinear(vec4(max(acrylicLinear, vec3(0.0)) * outputAlpha, outputAlpha), acrylicTransferFunction);
return mix(blurred, acrylic, shape);
}
void main() {
vec4 color = texture(tex, v_texcoord);
if (acrylicEnabled != 0)
color = applyAcrylic(color);
fragColor = blurFinish(color, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+79
View File
@@ -0,0 +1,79 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform float glassRefraction;
uniform float glassSize;
uniform float glassRoughness;
uniform vec2 glassPosition;
uniform float time;
uniform float auroraIntensity;
uniform vec4 auroraColor1;
uniform vec4 auroraColor2;
uniform int auroraTransferFunction;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "cm_helpers.glsl"
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
vec3 auroraSurface(vec2 position) {
float verticalWarp = sin(position.y * 0.19 - time) * 0.78 + sin(position.y * 0.43 + time * 2.0) * 0.24;
float warpedX = position.x + verticalWarp;
float broadPhase = warpedX * 0.58 + time;
float narrowPhase = warpedX * 1.07 - time * 2.0 + sin(position.y * 0.13 + time) * 0.42;
float broadRibbon = 0.5 + 0.5 * sin(broadPhase);
float narrowRibbon = 0.5 + 0.5 * sin(narrowPhase);
broadRibbon *= broadRibbon;
narrowRibbon *= narrowRibbon;
float verticalLight = 0.72 + 0.28 * sin(position.y * 0.27 - time * 2.0 + sin(warpedX * 0.21));
float curtain = clamp((broadRibbon * 0.72 + narrowRibbon * 0.38) * verticalLight, 0.0, 1.0);
float colorMix = 0.5 + 0.5 * sin(warpedX * 0.31 - position.y * 0.09 + time * 3.0);
float height = broadRibbon * 0.68 + narrowRibbon * 0.31 + verticalLight * 0.1;
return vec3(height, curtain, colorMix);
}
void main() {
vec2 position = (gl_FragCoord.xy - glassPosition) / glassSize;
vec3 surface = auroraSurface(position);
vec2 normal = vec2(dFdx(surface.x), dFdy(surface.x)) * glassSize * 0.75;
normal /= max(1.0, length(normal));
vec2 uvStep = normal.x * dFdx(v_texcoord) + normal.y * dFdy(v_texcoord);
vec2 displacedUV = clamp(v_texcoord + glassRefraction * uvStep, vec2(0.0), vec2(1.0));
vec4 color = texture(tex, displacedUV);
vec4 palette = mix(auroraColor1, auroraColor2, surface.z);
float amount = clamp(auroraIntensity * surface.y, 0.0, 1.0);
vec3 linearColor = toLinearRGB(color.rgb / max(color.a, 0.001), auroraTransferFunction);
linearColor = mix(linearColor, palette.rgb / max(palette.a, 0.001), amount * palette.a * 0.3);
linearColor += palette.rgb * amount * 0.035;
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
float emboss = dot(normal, LIGHT_DIRECTION);
linearColor *= 1.0 + emboss * glassRoughness * 0.08;
color = fromLinear(vec4(max(linearColor, vec3(0.0)) * color.a, color.a), auroraTransferFunction);
fragColor = blurFinish(color, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+307
View File
@@ -0,0 +1,307 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform float glassRefraction;
uniform float glassSize;
uniform float glassRoughness;
uniform float time;
uniform vec2 dropsPosition;
uniform sampler2D sharpTex;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "glassFinish.glsl"
layout(location = 0) out vec4 fragColor;
const float TAU = 6.28318530718;
struct SDropSurface {
float height;
float clarity;
};
SDropSurface combineDropSurfaces(SDropSurface first, SDropSurface second) {
return SDropSurface(max(first.height, second.height), max(first.clarity, second.clarity));
}
vec4 dropRandom(vec2 cell, float seed) {
vec2 key = cell + vec2(seed, seed * 1.61803);
return vec4(hash(key + vec2(17.17, 91.73)), hash(key + vec2(63.31, 11.89)), hash(key + vec2(37.61, 53.47)),
hash(key + vec2(79.13, 41.27)));
}
float sphericalCap(vec2 offset) {
float distanceSquared = dot(offset, offset);
float edge = 1.0 - smoothstep(0.82, 1.0, distanceSquared);
return sqrt(max(0.0, 1.0 - distanceSquared)) * edge;
}
float pulse(float beginValue, float peakValue, float fadeValue, float endValue, float value) {
return smoothstep(beginValue, peakValue, value) * (1.0 - smoothstep(fadeValue, endValue, value));
}
float smootherStep(float beginValue, float endValue, float value) {
float progress = clamp((value - beginValue) / max(endValue - beginValue, 0.001), 0.0, 1.0);
return progress * progress * progress * (progress * (progress * 6.0 - 15.0) + 10.0);
}
float smootherStepVelocity(float beginValue, float endValue, float value) {
float progress = clamp((value - beginValue) / max(endValue - beginValue, 0.001), 0.0, 1.0);
float inverseProgress = 1.0 - progress;
return 30.0 * progress * progress * inverseProgress * inverseProgress / max(endValue - beginValue, 0.001);
}
float trailCenter(float offset, float center, vec3 randomValue) {
float result = center + sin(offset * mix(7.0, 12.0, randomValue.z)) * mix(0.018, 0.055, randomValue.x);
return result + sin(offset * 23.0) * 0.009 * randomValue.y;
}
SDropSurface staticRainLayer(vec2 position, float seed, float density) {
const vec2 CELL_SIZE = vec2(1.45, 3.2);
float column = floor(position.x / CELL_SIZE.x);
float columnShift = hash(vec2(column, seed + 9.71));
vec2 gridPosition = position / CELL_SIZE + vec2(0.0, columnShift);
vec2 cell = floor(gridPosition);
vec2 local = fract(gridPosition);
vec4 randomValue = dropRandom(cell, seed);
float presence = step(1.0 - density, randomValue.w);
if (presence <= 0.0)
return SDropSurface(0.0, 0.0);
vec2 center = vec2(mix(0.22, 0.78, randomValue.x), mix(0.18, 0.38, randomValue.y));
vec2 radius = vec2(mix(0.11, 0.17, randomValue.z), mix(0.075, 0.12, randomValue.x));
vec2 bodyOffset = local - center;
float verticalPosition = bodyOffset.y / radius.y;
float taper = mix(1.05, 0.58, smoothstep(-0.75, 0.95, verticalPosition));
float body = sphericalCap(vec2(bodyOffset.x / (radius.x * taper), verticalPosition));
float trailStart = center.y + radius.y * 0.55;
float trailLength = min(mix(0.3, 0.52, randomValue.y), 0.96 - trailStart);
float trailProgress = (local.y - trailStart) / max(trailLength, 0.001);
float trailWindow = smoothstep(0.0, 0.08, trailProgress) * (1.0 - smoothstep(0.82, 1.0, trailProgress));
float trailOffset = max(0.0, local.y - trailStart);
float trailX = trailCenter(trailOffset, center.x, randomValue.xyz);
float trailWidth = mix(radius.x * 0.3, radius.x * 0.12, clamp(trailProgress, 0.0, 1.0));
float trailDistance = abs(local.x - trailX) / max(trailWidth, 0.001);
float trailProfile = sqrt(max(0.0, 1.0 - trailDistance * trailDistance));
float breakup = mix(0.45, 1.0, smoothstep(-0.3, 0.25, sin((local.y + randomValue.z) * 43.0)));
float trail = trailProfile * trailWindow * breakup * 0.24;
float satelliteProgress = mix(0.28, 0.7, randomValue.z);
float satelliteY = trailStart + trailLength * satelliteProgress;
float satelliteOffset = satelliteY - trailStart;
float satelliteX = trailCenter(satelliteOffset, center.x, randomValue.xyz);
vec2 satelliteRadius = radius * mix(0.18, 0.3, randomValue.y);
float satellite = sphericalCap((local - vec2(satelliteX, satelliteY)) / satelliteRadius) * 0.55;
float height = max(body, max(trail, satellite));
float clarity = max(smoothstep(0.04, 0.34, body) * 0.72, max(smoothstep(0.015, 0.2, trail) * 0.36, smoothstep(0.04, 0.35, satellite) * 0.5));
return SDropSurface(height, clarity);
}
float stickSlipProgress(float phase, vec4 randomValue) {
float shift = (randomValue.z - 0.5) * 0.05;
float progress = 0.0;
progress += smootherStep(0.13 + shift, 0.28 + shift, phase) * 0.16;
progress += smootherStep(0.41 - shift, 0.58 - shift, phase) * 0.29;
progress += smootherStep(0.68 + shift, 0.91 + shift, phase) * 0.55;
return progress;
}
float slideAmount(float phase, vec4 randomValue) {
float shift = (randomValue.z - 0.5) * 0.05;
float velocity = smootherStepVelocity(0.13 + shift, 0.28 + shift, phase) * 0.16;
velocity += smootherStepVelocity(0.41 - shift, 0.58 - shift, phase) * 0.29;
velocity += smootherStepVelocity(0.68 + shift, 0.91 + shift, phase) * 0.55;
return clamp(velocity / 4.6, 0.0, 1.0);
}
float dropPath(float progress, float start, vec4 randomValue) {
float phase = randomValue.w * TAU;
float wave = sin(progress * mix(4.0, 7.0, randomValue.z) + phase) - sin(phase);
float drift = (randomValue.y - 0.5) * 0.12 * progress;
return clamp(start + drift + wave * mix(0.012, 0.035, randomValue.x), 0.2, 0.8);
}
SDropSurface movingDrop(vec2 local, vec2 cell, float seed, float density, float animationTime, float cycleRate) {
vec4 randomValue = dropRandom(cell, seed);
float presence = step(1.0 - density, randomValue.w);
if (presence <= 0.0)
return SDropSurface(0.0, 0.0);
float phaseOffset = hash(cell + vec2(seed + 43.17, 71.53));
float rate = (0.75 + floor(hash(cell + vec2(seed + 81.31, 19.47)) * 3.0) * 0.25) * cycleRate;
float phase = fract(animationTime * rate + phaseOffset);
float progress = stickSlipProgress(phase, randomValue);
float sliding = slideAmount(phase, randomValue);
float startX = mix(0.23, 0.77, randomValue.x);
float startY = mix(0.45, 0.82, randomValue.y);
float travelDistance = mix(0.42, 0.82, randomValue.z);
vec2 center = vec2(dropPath(progress, startX, randomValue), startY - travelDistance * progress);
float landing = smootherStep(0.0, 0.035, phase);
float impact = pulse(0.0, 0.014, 0.035, 0.075, phase);
float drain = smootherStep(0.9, 0.975, phase);
float bodyLife = landing * (1.0 - smootherStep(0.955, 0.985, phase));
float scale = mix(0.58, 1.0, landing) * (1.0 + impact * 0.12) * mix(1.0, 0.28, drain);
vec2 baseRadius = vec2(mix(0.11, 0.17, randomValue.z), mix(0.075, 0.12, randomValue.x));
vec2 radius = baseRadius * vec2(mix(1.0, 0.82, sliding), mix(1.0, 1.24, sliding)) * scale;
vec2 bodyOffset = local - center;
float verticalPosition = bodyOffset.y / radius.y;
float taper = mix(1.05, 0.58, smoothstep(-0.75, 0.95, verticalPosition));
float body = sphericalCap(vec2(bodyOffset.x / (radius.x * taper), verticalPosition)) * bodyLife * mix(1.0, 0.42, drain);
float historicalProgress = (startY - local.y) / travelDistance;
float trailWindow = smoothstep(-0.015, 0.015, historicalProgress) *
(1.0 - smoothstep(max(0.0, progress - 0.015), progress + 0.015, historicalProgress));
float trailProgress = clamp(historicalProgress, 0.0, progress);
float trailX = dropPath(trailProgress, startX, randomValue);
float trailWidth = mix(baseRadius.x * 0.13, baseRadius.x * 0.27, clamp(trailProgress / max(progress, 0.001), 0.0, 1.0));
float trailDistance = abs(local.x - trailX) / max(trailWidth, 0.001);
float trailProfile = sqrt(max(0.0, 1.0 - trailDistance * trailDistance));
float trailAge = mix(0.35, 1.0, clamp(trailProgress / max(progress, 0.001), 0.0, 1.0));
float breakup = mix(0.42, 1.0, smoothstep(-0.35, 0.2, sin((local.y + randomValue.z) * 47.0)));
float trailLife = landing * (1.0 - smootherStep(0.965, 1.0, phase)) * smootherStep(0.025, 0.12, progress);
float trail = trailProfile * trailWindow * trailAge * breakup * trailLife * 0.24;
float satelliteProgress = progress * mix(0.28, 0.62, randomValue.z);
vec2 satelliteCenter = vec2(dropPath(satelliteProgress, startX, randomValue), startY - travelDistance * satelliteProgress);
vec2 satelliteRadius = baseRadius * mix(0.18, 0.29, randomValue.y);
float satellite = sphericalCap((local - satelliteCenter) / satelliteRadius) * trailLife * 0.5;
float height = max(body, max(trail, satellite));
float clarity = max(smoothstep(0.04, 0.34, body) * 0.76, max(smoothstep(0.015, 0.2, trail) * 0.42, smoothstep(0.04, 0.35, satellite) * 0.54));
return SDropSurface(height, clarity);
}
SDropSurface movingRainLayer(vec2 position, float seed, float density, float animationTime, float cycleRate) {
const vec2 CELL_SIZE = vec2(1.45, 3.2);
float column = floor(position.x / CELL_SIZE.x);
float columnShift = hash(vec2(column, seed + 9.71));
vec2 gridPosition = position / CELL_SIZE + vec2(0.0, columnShift);
vec2 cell = floor(gridPosition);
vec2 local = fract(gridPosition);
SDropSurface drop = movingDrop(local, cell, seed, density, animationTime, cycleRate);
SDropSurface dropFromAbove = SDropSurface(0.0, 0.0);
if (local.y > 0.4)
dropFromAbove = movingDrop(local - vec2(0.0, 1.0), cell + vec2(0.0, 1.0), seed, density, animationTime, cycleRate);
return combineDropSurfaces(drop, dropFromAbove);
}
SDropSurface rainLayer(vec2 position, float seed, float density, float animationTime, float cycleRate) {
if (animationTime <= 0.0)
return staticRainLayer(position, seed, density);
return movingRainLayer(position, seed, density, animationTime, cycleRate);
}
SDropSurface beadLayer(vec2 position, float seed, float density, float animationTime) {
vec2 cell = floor(position);
vec2 local = fract(position);
vec4 randomValue = dropRandom(cell, seed);
float presence = step(1.0 - density, randomValue.w);
if (presence <= 0.0)
return SDropSurface(0.0, 0.0);
vec2 center = mix(vec2(0.2), vec2(0.8), randomValue.xy);
float radius = mix(0.08, 0.19, randomValue.z * randomValue.z);
vec2 radii = vec2(radius * mix(0.82, 1.08, randomValue.x), radius * mix(0.9, 1.22, randomValue.y));
float amplitude = mix(0.42, 0.78, randomValue.z);
if (animationTime <= 0.0) {
float height = sphericalCap((local - center) / radii) * amplitude;
return SDropSurface(height, smoothstep(0.04, 0.36, height) * 0.46);
}
float behavior = hash(cell + vec2(seed + 57.91, 13.37));
float persistent = step(0.48, behavior);
float phaseOffset = hash(cell + vec2(seed + 23.73, 89.11));
float rate = 0.75 + floor(hash(cell + vec2(seed + 67.19, 31.43)) * 3.0) * 0.25;
float phase = fract(animationTime * rate + phaseOffset);
float landing = smootherStep(0.0, 0.03, phase);
float impact = pulse(0.0, 0.012, 0.03, 0.07, phase) * (1.0 - persistent);
float transientLife = landing * (1.0 - smootherStep(0.84, 1.0, phase));
float life = mix(transientLife, 1.0, persistent);
float transientRadiusScale = mix(0.48, 1.0, landing) * (1.0 + impact * 0.16);
float edgeDistance = min(min(center.x, 1.0 - center.x), min(center.y, 1.0 - center.y));
float maximumRadius = max(radii.x, radii.y);
float persistentRadiusScale = min(1.0, edgeDistance / max(maximumRadius, 0.001));
transientRadiusScale = min(transientRadiusScale, edgeDistance / max(maximumRadius * 1.7, 0.001));
float radiusScale = mix(transientRadiusScale, persistentRadiusScale, persistent);
vec2 normalizedOffset = (local - center) / (radii * max(radiusScale, 0.16));
float body = sphericalCap(normalizedOffset) * life;
float ringDistance = abs(length(normalizedOffset) - 1.4);
float ring = (1.0 - smoothstep(0.1, 0.26, ringDistance)) * impact * 0.08;
float height = max(body, ring) * amplitude;
float clarity = max(smoothstep(0.04, 0.36, body * amplitude) * 0.48, smoothstep(0.01, 0.06, ring * amplitude) * 0.16);
return SDropSurface(height, clarity);
}
SDropSurface dropsSurface(vec2 position) {
SDropSurface largeDrops = rainLayer(position, 3.17, 0.55, time, 0.45);
SDropSurface smallDrops = rainLayer(position * 1.43 + vec2(0.37, 1.91), 17.83, 0.35, time, 1.0);
SDropSurface beads = beadLayer(position * 1.75 + vec2(4.13, 2.71), 31.41, 0.36, time);
smallDrops.height *= 0.78;
smallDrops.clarity *= 0.74;
return combineDropSurfaces(largeDrops, combineDropSurfaces(smallDrops, beads));
}
vec4 dropsFinish(vec2 normal, float clarity) {
normal /= max(1.0, length(normal));
vec2 uvStep = normal.x * dFdx(v_texcoord) + normal.y * dFdy(v_texcoord);
vec2 displacedUV = clamp(v_texcoord + glassRefraction * uvStep, vec2(0.0), vec2(1.0));
vec4 pixColor = mix(texture(tex, displacedUV), texture(sharpTex, displacedUV), clarity);
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
float emboss = dot(normal, LIGHT_DIRECTION);
pixColor.rgb *= 1.0 + emboss * glassRoughness * 0.12;
return blurFinish(pixColor, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
void main() {
vec2 fragmentPosition = gl_FragCoord.xy - dropsPosition;
vec2 position = vec2(fragmentPosition.x, -fragmentPosition.y) / glassSize;
SDropSurface surface = dropsSurface(position);
vec2 gradient;
if (time <= 0.0)
gradient = vec2(dFdx(surface.height), dFdy(surface.height));
else {
float pixelStep = 1.0 / glassSize;
float horizontalHeight = dropsSurface(position + vec2(pixelStep, 0.0)).height;
float verticalHeight = dropsSurface(position - vec2(0.0, pixelStep)).height;
gradient = vec2(horizontalHeight - surface.height, verticalHeight - surface.height);
}
gradient *= glassSize * 0.16;
fragColor = dropsFinish(gradient, surface.clarity);
}
+136
View File
@@ -0,0 +1,136 @@
#ifndef ALLOW_INCLUDES
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
#endif
precision highp usampler2D;
const float FLUID_JAR_PI = 3.14159265;
const ivec2 FLUID_JAR_STATE_SLOT_SIZE = ivec2(4, 1);
const ivec2 FLUID_JAR_GRAPH_SLOT_SIZE = ivec2(8, 1);
struct FluidJarParticle {
int id;
vec2 position;
vec2 velocity;
float pressure;
float density;
float smoothScale;
float divergence;
vec4 data;
};
ivec2 fluidJarAddress(int id, int field, ivec2 gridSize) {
return FLUID_JAR_STATE_SLOT_SIZE * ivec2(id % gridSize.x, id / gridSize.x) + ivec2(field, 0);
}
ivec2 fluidJarGraphAddress(int id, int direction, int pairIndex, ivec2 gridSize) {
return FLUID_JAR_GRAPH_SLOT_SIZE * ivec2(id % gridSize.x, id / gridSize.x) + ivec2(direction * 2 + pairIndex, 0);
}
uvec2 fluidJarEncodeId(int id) {
if (id < 0)
return uvec2(0u);
uint value = uint(id) + 1u;
return uvec2(value & 65535u, value >> 16u);
}
int fluidJarDecodeId(uvec2 encoded) {
uint value = encoded.x | (encoded.y << 16u);
return value == 0u ? -1 : int(value - 1u);
}
uvec4 fluidJarEncodeIdPair(ivec2 ids) {
return uvec4(fluidJarEncodeId(ids.x), fluidJarEncodeId(ids.y));
}
ivec2 fluidJarDecodeIdPair(uvec4 encoded) {
return ivec2(fluidJarDecodeId(encoded.rg), fluidJarDecodeId(encoded.ba));
}
ivec4 fluidJarLoadNeighbors(usampler2D graphTex, int id, int direction, ivec2 gridSize) {
ivec2 first = fluidJarDecodeIdPair(texelFetch(graphTex, fluidJarGraphAddress(id, direction, 0, gridSize), 0));
ivec2 second = fluidJarDecodeIdPair(texelFetch(graphTex, fluidJarGraphAddress(id, direction, 1, gridSize), 0));
return ivec4(first, second);
}
FluidJarParticle fluidJarLoadParticle(sampler2D particleTex, int id, ivec2 gridSize) {
FluidJarParticle particle;
vec4 value = texelFetch(particleTex, fluidJarAddress(id, 0, gridSize), 0);
particle.position = value.xy;
particle.velocity = value.zw;
value = texelFetch(particleTex, fluidJarAddress(id, 1, gridSize), 0);
particle.pressure = value.x;
particle.density = value.y;
particle.smoothScale = value.z;
particle.divergence = value.w;
particle.data = texelFetch(particleTex, fluidJarAddress(id, 2, gridSize), 0);
particle.id = id;
return particle;
}
vec4 fluidJarSaveParticle(FluidJarParticle particle, int field) {
if (field == 0)
return vec4(particle.position, particle.velocity);
if (field == 1)
return vec4(particle.pressure, particle.density, particle.smoothScale, particle.divergence);
if (field == 2)
return particle.data;
return vec4(0.0);
}
void fluidJarResolveBoundaries(inout FluidJarParticle particle, vec2 resolution, vec4 wallVelocities, float mass) {
float restitution = clamp(0.12 / sqrt(max(mass, 0.1)), 0.02, 0.4);
float left = min(2.0, resolution.x * 0.5);
float right = max(resolution.x - 2.0, left);
float bottom = min(2.0, resolution.y);
float top = max(bottom, resolution.y);
if (particle.position.x < left) {
particle.position.x = left;
if (particle.velocity.x < wallVelocities.x)
particle.velocity.x = wallVelocities.x - restitution * (particle.velocity.x - wallVelocities.x);
}
if (particle.position.x > right) {
particle.position.x = right;
if (particle.velocity.x > wallVelocities.y)
particle.velocity.x = wallVelocities.y - restitution * (particle.velocity.x - wallVelocities.y);
}
if (particle.position.y < bottom) {
particle.position.y = bottom;
if (particle.velocity.y < wallVelocities.z)
particle.velocity.y = wallVelocities.z - restitution * (particle.velocity.y - wallVelocities.z);
}
if (particle.position.y > top) {
particle.position.y = top;
if (particle.velocity.y > wallVelocities.w)
particle.velocity.y = wallVelocities.w - restitution * (particle.velocity.y - wallVelocities.w);
}
}
float fluidJarSquared(float value) {
return value * value + 1e-2;
}
float fluidJarKernel(float distanceValue, float scale) {
return exp(-fluidJarSquared(distanceValue / scale)) / (FLUID_JAR_PI * fluidJarSquared(scale));
}
float fluidJarKernelGradient(float distanceValue, float scale) {
return 2.0 * distanceValue * fluidJarKernel(distanceValue, scale) / fluidJarSquared(scale);
}
float fluidJarHash13(vec3 value) {
value = fract(value * 0.1031);
value += dot(value, value.yzx + 33.33);
return fract((value.x + value.y) * value.z);
}
ivec2 fluidJarCrossDistribution(int index) {
return (1 << (index / 4)) * ivec2(((index & 2) / 2) ^ 1, (index & 2) / 2) * (2 * (index % 2) - 1);
}
+170
View File
@@ -0,0 +1,170 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform sampler2D sharpTex;
uniform sampler2D fluidJarVisualTex;
uniform int fluidJarEnabled;
uniform vec4 fluidJarExtent;
uniform vec4 fluidJarOutputTransform;
uniform vec2 fluidJarOutputOffset;
uniform vec2 fluidJarLogicalSize;
uniform vec4 fluidJarColor;
uniform float fluidJarRefraction;
uniform int fluidJarTransferFunction;
uniform float fluidJarStrength;
uniform float fluidJarTurbulence;
uniform float fluidJarDistortion;
uniform float time;
uniform float noise;
uniform float brightness;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "cm_helpers.glsl"
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
vec4 sampleFluid(vec2 uv) {
return texture(fluidJarVisualTex, clamp(uv, vec2(0.0), vec2(1.0)));
}
vec2 outputToLogical(vec2 position) {
return vec2(dot(fluidJarOutputTransform.xy, position), dot(fluidJarOutputTransform.zw, position)) + fluidJarOutputOffset;
}
vec2 logicalToOutputVector(vec2 vector) {
return vec2(dot(fluidJarOutputTransform.xz, vector), dot(fluidJarOutputTransform.yw, vector));
}
vec2 shimmerWaves(vec2 position, float material) {
const vec2 DIRECTION_A = vec2(0.894427, 0.447214);
const vec2 DIRECTION_B = vec2(-0.351123, 0.936329);
const vec2 DIRECTION_C = vec2(0.196116, -0.980581);
float materialPhase = 1.4 * material;
float waveA = cos(0.115 * dot(position, DIRECTION_A) + 0.73 * time + materialPhase);
float waveB = cos(0.168 * dot(position, DIRECTION_B) - 0.51 * time - 0.8 * materialPhase);
float waveC = cos(0.237 * dot(position, DIRECTION_C) + 0.37 * time + 1.3 * materialPhase);
return 0.46 * DIRECTION_A * waveA + 0.34 * DIRECTION_B * waveB + 0.20 * DIRECTION_C * waveC;
}
vec4 applyLiquid(vec4 blurred, vec2 fluidUV, vec2 sourceSize, vec2 sourcePosition, vec2 logicalPosition) {
vec2 visualSize = vec2(textureSize(fluidJarVisualTex, 0));
vec2 texel = 1.0 / visualSize;
vec2 cellPixels = fluidJarLogicalSize / visualSize;
vec4 center = sampleFluid(fluidUV);
vec4 left = sampleFluid(fluidUV - vec2(texel.x, 0.0));
vec4 right = sampleFluid(fluidUV + vec2(texel.x, 0.0));
vec4 down = sampleFluid(fluidUV - vec2(0.0, texel.y));
vec4 up = sampleFluid(fluidUV + vec2(0.0, texel.y));
float field = (4.0 * center.a + left.a + right.a + down.a + up.a) * 0.125;
float mask = smoothstep(0.18, 0.42, field);
float opacity = clamp(fluidJarColor.a, 0.0, 1.0);
float effect = opacity * clamp(fluidJarStrength, 0.0, 1.0);
if (mask <= 0.0 || effect <= 0.0)
return blurred;
vec2 gradient = vec2((right.a - left.a) / max(2.0 * cellPixels.x, 0.001), -(up.a - down.a) / max(2.0 * cellPixels.y, 0.001));
float gradientLength = length(gradient);
vec2 outwardNormal = gradientLength > 1e-5 ? -gradient / gradientLength : vec2(0.0);
float edge = pow(clamp(4.0 * mask * (1.0 - mask), 0.0, 1.0), 0.75) * smoothstep(0.0001, 0.01, gradientLength);
vec2 centerVelocity = vec2(center.r * cellPixels.x, -center.g * cellPixels.y) * 1.5;
vec2 leftVelocity = vec2(left.r * cellPixels.x, -left.g * cellPixels.y) * 1.5;
vec2 rightVelocity = vec2(right.r * cellPixels.x, -right.g * cellPixels.y) * 1.5;
vec2 downVelocity = vec2(down.r * cellPixels.x, -down.g * cellPixels.y) * 1.5;
vec2 upVelocity = vec2(up.r * cellPixels.x, -up.g * cellPixels.y) * 1.5;
vec2 outputVelocity = (2.0 * centerVelocity + leftVelocity + rightVelocity + downVelocity + upVelocity) / 6.0;
float speed = length(outputVelocity);
vec2 flowPixels = 2.0 * outputVelocity / (0.8 + speed);
float motion = smoothstep(0.05, 0.8, speed);
float turbulence = clamp(fluidJarTurbulence, 0.0, 5.0);
vec2 turbulentPixels = vec2(0.0);
if (turbulence > 0.0) {
float curl = (rightVelocity.y - leftVelocity.y) / max(2.0 * cellPixels.x, 0.001) - (downVelocity.x - upVelocity.x) / max(2.0 * cellPixels.y, 0.001);
float normalizedCurl = tanh(curl / 0.12);
vec2 swirlPixels = normalizedCurl * vec2(-outputVelocity.y, outputVelocity.x) / (0.5 + speed);
vec2 materialGradient = vec2((right.b - left.b) / max(2.0 * cellPixels.x, 0.001), -(up.b - down.b) / max(2.0 * cellPixels.y, 0.001));
vec2 materialPixels = 4.0 * min(cellPixels.x, cellPixels.y) * materialGradient * mix(0.65, 1.35, motion) * (1.0 + 0.5 * abs(normalizedCurl));
float shimmerActivity = mix(0.24, 1.25, motion) * (1.0 + 0.35 * abs(normalizedCurl));
vec2 shimmerPixels = 1.6 * shimmerActivity * shimmerWaves(logicalPosition, center.b);
turbulentPixels = turbulence * (materialPixels + swirlPixels + shimmerPixels);
}
float interior = smoothstep(0.45, 0.8, mask) * (1.0 - edge);
vec2 interiorPixels = interior * (flowPixels + turbulentPixels);
interiorPixels /= max(1.0, length(interiorPixels) / max(fluidJarRefraction, 0.001));
float edgePixels = mix(5.0, fluidJarRefraction, motion);
float distortion = clamp(fluidJarDistortion, 0.0, 10.0);
float maximumDisplacement = fluidJarRefraction * distortion;
vec2 displacementPixels = distortion * effect * (mask * interiorPixels + edge * outwardNormal * edgePixels);
float sourceEdgeDistance = min(min(sourcePosition.x, sourceSize.x - sourcePosition.x), min(sourcePosition.y, sourceSize.y - sourcePosition.y));
displacementPixels *= smoothstep(0.0, maximumDisplacement + 1.0, sourceEdgeDistance);
displacementPixels /= max(1.0, length(displacementPixels) / max(maximumDisplacement, 0.001));
displacementPixels = logicalToOutputVector(displacementPixels);
vec3 surfaceNormal = normalize(vec3(-gradient * 20.0, 1.0));
float oneMinusNV = 1.0 - max(surfaceNormal.z, 0.0);
float fresnel = 0.0204 + 0.9796 * pow(oneMinusNV, 5.0);
vec2 halfTexel = 0.5 / sourceSize;
vec2 refractedUV = clamp(v_texcoord + displacementPixels / sourceSize, halfTexel, vec2(1.0) - halfTexel);
vec4 refracted = texture(sharpTex, refractedUV);
float sharpAmount = effect * mask * mix(0.98, 0.78, fresnel);
float outputAlpha = blurred.a;
vec3 blurredLinear = toLinearRGB(blurred.rgb / max(blurred.a, 0.001), fluidJarTransferFunction);
vec3 refractedLinear = toLinearRGB(refracted.rgb / max(refracted.a, 0.001), fluidJarTransferFunction);
vec3 liquidLinear = mix(blurredLinear, refractedLinear, sharpAmount);
vec3 tintLinear = toLinearRGB(fluidJarColor.rgb, CM_TRANSFER_FUNCTION_SRGB);
float opticalDepth = -log(max(1.0 - opacity, 0.0001));
float thickness = clamp(fluidJarStrength, 0.0, 1.0) * mask * mix(0.25, 1.0, field);
float transmission = exp(-opticalDepth * thickness);
liquidLinear = liquidLinear * transmission + tintLinear * (1.0 - transmission);
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
vec3 light = normalize(vec3(LIGHT_DIRECTION, 0.75));
vec3 halfway = normalize(light + vec3(0.0, 0.0, 1.0));
float specular = pow(max(dot(surfaceNormal, halfway), 0.0), 32.0);
float directional = max(dot(outwardNormal, LIGHT_DIRECTION), 0.0);
float highlight = effect * edge * (0.04 + 0.35 * fresnel + 0.18 * specular + 0.04 * directional);
liquidLinear += mix(vec3(1.0), tintLinear, 0.12) * highlight;
return fromLinear(vec4(liquidLinear * outputAlpha, outputAlpha), fluidJarTransferFunction);
}
void main() {
vec4 color = texture(tex, v_texcoord);
if (fluidJarEnabled != 0) {
vec2 sourceSize = vec2(textureSize(tex, 0));
vec2 position = v_texcoord * sourceSize;
vec2 outputUV = (position - fluidJarExtent.xy) / fluidJarExtent.zw;
if (all(greaterThanEqual(outputUV, vec2(0.0))) && all(lessThanEqual(outputUV, vec2(1.0)))) {
vec2 logicalUV = outputToLogical(outputUV);
vec2 fluidUV = logicalUV;
fluidUV.y = 1.0 - fluidUV.y;
color = applyLiquid(color, fluidUV, sourceSize, position, logicalUV * fluidJarLogicalSize);
}
}
fragColor = blurFinish(color, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+126
View File
@@ -0,0 +1,126 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
precision highp usampler2D;
in vec2 v_texcoord;
uniform sampler2D fluidJarParticleTex;
uniform usampler2D fluidJarGraphTex;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
uniform int fluidJarFrame;
#include "fluidJar.glsl"
layout(location = 0) out uvec4 fragColor;
ivec4 nearestIds = ivec4(-1);
vec4 nearestDistances = vec4(1e6);
void insertNearest(float distanceValue, int id) {
if (nearestDistances.x > distanceValue) {
nearestDistances = vec4(distanceValue, nearestDistances.xyz);
nearestIds = ivec4(id, nearestIds.xyz);
} else if (nearestDistances.y > distanceValue) {
nearestDistances.yzw = vec3(distanceValue, nearestDistances.yz);
nearestIds.yzw = ivec3(id, nearestIds.yz);
} else if (nearestDistances.z > distanceValue) {
nearestDistances.zw = vec2(distanceValue, nearestDistances.z);
nearestIds.zw = ivec2(id, nearestIds.z);
} else if (nearestDistances.w > distanceValue) {
nearestDistances.w = distanceValue;
nearestIds.w = id;
}
}
bool alreadySorted(int id, int currentId) {
return id < 0 || id >= fluidJarParticleCount || id == currentId || any(equal(nearestIds, ivec4(id)));
}
int neighborDirection(vec2 delta, int firstId, int secondId) {
if (dot(delta, delta) < 1e-6) {
float pairHash = fluidJarHash13(vec3(float(min(firstId, secondId)), float(max(firstId, secondId)), 0.731));
int pairDirection = min(int(pairHash * 4.0), 3);
return firstId < secondId ? pairDirection : pairDirection ^ 1;
}
if (abs(delta.x) >= abs(delta.y))
return delta.x >= 0.0 ? 0 : 1;
return delta.y >= 0.0 ? 2 : 3;
}
void sortCandidate(int candidate, int direction, FluidJarParticle particle, ivec2 gridSize) {
if (alreadySorted(candidate, particle.id))
return;
vec2 neighborPosition = texelFetch(fluidJarParticleTex, fluidJarAddress(candidate, 0, gridSize), 0).xy;
vec2 delta = neighborPosition - particle.position;
if (neighborDirection(delta, particle.id, candidate) != direction)
return;
insertNearest(length(delta), candidate);
}
void sortGridCandidate(int row, int columnOffset, int direction, FluidJarParticle particle, ivec2 gridSize) {
if (row < 0 || row >= gridSize.y)
return;
int rowStart = row * gridSize.x;
int rowCount = min(gridSize.x, fluidJarParticleCount - rowStart);
if (rowCount <= 0)
return;
vec2 spacing = fluidJarResolution / vec2(gridSize);
float centering = 0.5 * float(gridSize.x - rowCount);
int column = clamp(int(particle.position.x / spacing.x - centering) + columnOffset, 0, rowCount - 1);
sortCandidate(rowStart + column, direction, particle, gridSize);
}
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
ivec2 storageSize = FLUID_JAR_GRAPH_SLOT_SIZE * gridSize;
if (any(greaterThanEqual(pixel, storageSize))) {
fragColor = uvec4(0u);
return;
}
ivec2 cell = pixel / FLUID_JAR_GRAPH_SLOT_SIZE;
int id = cell.x + cell.y * gridSize.x;
int field = pixel.x % FLUID_JAR_GRAPH_SLOT_SIZE.x;
int direction = field / 2;
int pairIndex = field % 2;
if (id >= fluidJarParticleCount) {
fragColor = uvec4(0u);
return;
}
FluidJarParticle particle = fluidJarLoadParticle(fluidJarParticleTex, id, gridSize);
int estimatedRow = min(int(particle.position.y / (fluidJarResolution.y / float(gridSize.y))), (fluidJarParticleCount - 1) / gridSize.x);
for (int offset = -4; offset <= 4; ++offset)
sortGridCandidate(estimatedRow + offset, 0, direction, particle, gridSize);
for (int offset = 1; offset <= 2; ++offset) {
sortGridCandidate(estimatedRow, -offset, direction, particle, gridSize);
sortGridCandidate(estimatedRow, offset, direction, particle, gridSize);
}
for (int index = 0; index < 8; ++index)
sortCandidate(int(float(fluidJarParticleCount) * fluidJarHash13(vec3(float(fluidJarFrame), float(id), float(index)))), direction, particle, gridSize);
ivec4 directNeighbors = fluidJarLoadNeighbors(fluidJarGraphTex, id, direction, gridSize);
for (int index = 0; index < 4; ++index) {
int neighborId = directNeighbors[index];
sortCandidate(neighborId, direction, particle, gridSize);
if (neighborId < 0 || neighborId >= fluidJarParticleCount)
continue;
ivec4 indirectNeighbors = fluidJarLoadNeighbors(fluidJarGraphTex, neighborId, (fluidJarFrame + id) % 4, gridSize);
for (int indirect = 0; indirect < 2; ++indirect)
sortCandidate(indirectNeighbors[indirect], direction, particle, gridSize);
}
fragColor = fluidJarEncodeIdPair(pairIndex == 0 ? nearestIds.xy : nearestIds.zw);
}
@@ -0,0 +1,21 @@
#version 300 es
precision highp float;
in vec2 v_texcoord;
uniform sampler2D fluidJarHistoryTex;
uniform vec2 fluidJarOldResolution;
uniform vec4 fluidJarHistoryTransform;
uniform vec4 fluidJarHistoryFallback;
layout(location = 0) out vec4 fragColor;
void main() {
vec2 oldPosition = gl_FragCoord.xy * fluidJarHistoryTransform.xy + fluidJarHistoryTransform.zw;
if (any(lessThan(oldPosition, vec2(0.0))) || any(greaterThanEqual(oldPosition, fluidJarOldResolution))) {
fragColor = fluidJarHistoryFallback;
return;
}
fragColor = texture(fluidJarHistoryTex, oldPosition / fluidJarOldResolution);
}
+52
View File
@@ -0,0 +1,52 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
in vec2 v_texcoord;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
#include "fluidJar.glsl"
layout(location = 0) out vec4 fragColor;
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
ivec2 storageSize = FLUID_JAR_STATE_SLOT_SIZE * gridSize;
if (any(greaterThanEqual(pixel, storageSize))) {
fragColor = vec4(-1.0);
return;
}
ivec2 cell = pixel / FLUID_JAR_STATE_SLOT_SIZE;
int id = cell.x + cell.y * gridSize.x;
int field = pixel.x % FLUID_JAR_STATE_SLOT_SIZE.x;
if (id >= fluidJarParticleCount) {
fragColor = vec4(-1.0);
return;
}
FluidJarParticle particle;
particle.id = id;
vec2 spacing = fluidJarResolution / vec2(gridSize);
particle.position = (vec2(cell) + vec2(0.5)) * spacing;
int partialRowCount = fluidJarParticleCount % gridSize.x;
if (partialRowCount > 0 && cell.y == fluidJarParticleCount / gridSize.x)
particle.position.x += 0.5 * float(gridSize.x - partialRowCount) * spacing.x;
particle.velocity = vec2(0.0);
particle.pressure = 0.0;
particle.density = 5.0;
particle.smoothScale = 1.0;
particle.divergence = 0.0;
float materialA = 0.5 + 0.25 * sin(dot(particle.position, vec2(0.17, 0.11))) + 0.25 * sin(dot(particle.position, vec2(-0.09, 0.23)) + 1.7);
float materialB = 0.5 + 0.25 * sin(dot(particle.position, vec2(-0.13, 0.07)) + 0.8) + 0.25 * sin(dot(particle.position, vec2(0.05, 0.19)) + 2.4);
float shapeA = fluidJarHash13(vec3(float(id) + 0.37, 1.73, 4.91));
float shapeB = fluidJarHash13(vec3(float(id) + 2.11, 5.29, 0.83));
particle.data = vec4(clamp(materialA, 0.0, 1.0), clamp(materialB, 0.0, 1.0), shapeA, shapeB);
fragColor = fluidJarSaveParticle(particle, field);
}
@@ -0,0 +1,50 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
in vec2 v_texcoord;
uniform sampler2D fluidJarParticleTex;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
uniform vec2 fluidJarOldGridSize;
uniform int fluidJarOldParticleCount;
uniform vec4 fluidJarTransform;
uniform vec2 fluidJarVelocityScale;
uniform vec4 fluidJarWallVelocities;
uniform float fluidJarMass;
#include "fluidJar.glsl"
layout(location = 0) out vec4 fragColor;
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
ivec2 storageSize = FLUID_JAR_STATE_SLOT_SIZE * gridSize;
if (any(greaterThanEqual(pixel, storageSize))) {
fragColor = vec4(-1.0);
return;
}
ivec2 cell = pixel / FLUID_JAR_STATE_SLOT_SIZE;
int id = cell.x + cell.y * gridSize.x;
int field = pixel.x % FLUID_JAR_STATE_SLOT_SIZE.x;
if (id >= fluidJarParticleCount || id >= fluidJarOldParticleCount) {
fragColor = vec4(-1.0);
return;
}
FluidJarParticle particle = fluidJarLoadParticle(fluidJarParticleTex, id, ivec2(fluidJarOldGridSize));
particle.position = particle.position * fluidJarTransform.xy + fluidJarTransform.zw;
particle.velocity *= fluidJarVelocityScale;
if (any(greaterThan(abs(fluidJarTransform.xy - vec2(1.0)), vec2(0.001)))) {
particle.pressure = 0.0;
particle.divergence = 0.0;
}
fluidJarResolveBoundaries(particle, fluidJarResolution, fluidJarWallVelocities, fluidJarMass);
fragColor = fluidJarSaveParticle(particle, field);
}
+89
View File
@@ -0,0 +1,89 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
precision highp usampler2D;
in vec2 v_texcoord;
uniform sampler2D fluidJarParticleTex;
uniform usampler2D fluidJarGraphTex;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
uniform float fluidJarDt;
uniform vec4 fluidJarWallVelocities;
uniform float fluidJarMass;
#include "fluidJar.glsl"
layout(location = 0) out vec4 fragColor;
bool isReciprocalNeighbor(int particleId, int neighborId, int direction, ivec2 gridSize) {
return any(equal(fluidJarLoadNeighbors(fluidJarGraphTex, neighborId, direction ^ 1, gridSize), ivec4(particleId)));
}
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
ivec2 storageSize = FLUID_JAR_STATE_SLOT_SIZE * gridSize;
if (any(greaterThanEqual(pixel, storageSize))) {
fragColor = vec4(0.0);
return;
}
ivec2 cell = pixel / FLUID_JAR_STATE_SLOT_SIZE;
int id = cell.x + cell.y * gridSize.x;
int field = pixel.x % FLUID_JAR_STATE_SLOT_SIZE.x;
if (id >= fluidJarParticleCount) {
fragColor = vec4(0.0);
return;
}
FluidJarParticle particle = fluidJarLoadParticle(fluidJarParticleTex, id, gridSize);
vec2 force = vec2(0.0, -0.001);
float scale = 0.21 / 0.036;
float divergence = 0.0;
float density = fluidJarKernel(0.0, scale);
vec2 averageMaterial = particle.data.xy;
float neighborCount = 1.0;
for (int direction = 0; direction < 4; ++direction) {
ivec4 neighbors = fluidJarLoadNeighbors(fluidJarGraphTex, id, direction, gridSize);
for (int index = 0; index < 4; ++index) {
int neighborId = neighbors[index];
if (neighborId < 0 || neighborId >= fluidJarParticleCount)
continue;
if (!isReciprocalNeighbor(id, neighborId, direction, gridSize))
continue;
FluidJarParticle neighbor = fluidJarLoadParticle(fluidJarParticleTex, neighborId, gridSize);
float distanceValue = distance(particle.position, neighbor.position);
vec2 velocityDelta = neighbor.velocity - particle.velocity;
vec2 positionDelta = neighbor.position - particle.position;
vec2 directionVector = positionDelta / (distanceValue + 0.001);
float kernel = fluidJarKernel(distanceValue, scale);
float velocityProjection = dot(directionVector, velocityDelta);
vec2 pressureForce = -(neighbor.pressure / fluidJarSquared(neighbor.density) + particle.pressure / fluidJarSquared(particle.density)) * directionVector * kernel;
divergence += velocityProjection * kernel;
density += kernel;
averageMaterial += neighbor.data.xy;
vec2 viscosity = 1.4 * (3.0 + 3.0 * length(velocityDelta)) * directionVector * velocityProjection * kernel;
force += pressureForce / fluidJarMass + viscosity;
neighborCount += 1.0;
}
}
particle.density = density;
particle.divergence = divergence;
particle.smoothScale = 0.0;
float waterPressure = 0.035 * 0.036 * (pow(abs(particle.density / 0.036), 7.0) - 1.0);
particle.pressure = clamp(waterPressure, 0.0, 0.04);
particle.velocity += force * fluidJarDt;
particle.velocity -= particle.velocity * (0.5 * tanh(8.0 * (length(particle.velocity) - 1.5)) + 0.5);
particle.position += particle.velocity * fluidJarDt;
fluidJarResolveBoundaries(particle, fluidJarResolution, fluidJarWallVelocities, fluidJarMass);
particle.data.xy = mix(particle.data.xy, averageMaterial / neighborCount, 0.003);
fragColor = fluidJarSaveParticle(particle, field);
}
@@ -0,0 +1,84 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
precision highp usampler2D;
in vec2 v_texcoord;
uniform sampler2D fluidJarParticleTex;
uniform usampler2D fluidJarGraphTex;
uniform usampler2D fluidJarTrackingTex;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
uniform int fluidJarFrame;
#include "fluidJar.glsl"
layout(location = 0) out uvec4 fragColor;
float nearestDistance = 1e10;
int nearestId = -1;
vec2 position;
void sortCandidate(int candidate, ivec2 gridSize) {
if (candidate < 0 || candidate >= fluidJarParticleCount)
return;
vec2 particlePosition = texelFetch(fluidJarParticleTex, fluidJarAddress(candidate, 0, gridSize), 0).xy;
float candidateDistance = distance(position, particlePosition);
if (candidateDistance >= nearestDistance)
return;
nearestDistance = candidateDistance;
nearestId = candidate;
}
void sortGridCandidate(int row, ivec2 gridSize) {
if (row < 0 || row >= gridSize.y)
return;
int rowStart = row * gridSize.x;
int rowCount = min(gridSize.x, fluidJarParticleCount - rowStart);
if (rowCount <= 0)
return;
vec2 spacing = fluidJarResolution / vec2(gridSize);
float centering = 0.5 * float(gridSize.x - rowCount);
int column = clamp(int(position.x / spacing.x - centering), 0, rowCount - 1);
sortCandidate(rowStart + column, gridSize);
}
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
position = vec2(pixel) + vec2(0.5);
int previousId = fluidJarDecodeId(texelFetch(fluidJarTrackingTex, clamp(pixel, ivec2(0), ivec2(fluidJarResolution) - 1), 0).rg);
sortCandidate(previousId, gridSize);
int estimatedRow = min(int(position.y / (fluidJarResolution.y / float(gridSize.y))), (fluidJarParticleCount - 1) / gridSize.x);
sortGridCandidate(estimatedRow - 1, gridSize);
sortGridCandidate(estimatedRow, gridSize);
sortGridCandidate(estimatedRow + 1, gridSize);
for (int index = 0; index < 8; ++index) {
ivec2 samplePixel = clamp(pixel + fluidJarCrossDistribution(index), ivec2(0), ivec2(fluidJarResolution) - 1);
sortCandidate(fluidJarDecodeId(texelFetch(fluidJarTrackingTex, samplePixel, 0).rg), gridSize);
}
for (int index = 0; index < 5; ++index)
sortCandidate(int(float(fluidJarParticleCount) *
fluidJarHash13(vec3(float(fluidJarFrame + index) + 0.5, float(pixel.x) + 0.37, float(pixel.y) + 0.61))),
gridSize);
if (nearestId >= 0) {
for (int direction = 0; direction < 4; ++direction) {
ivec4 neighbors = fluidJarLoadNeighbors(fluidJarGraphTex, nearestId, direction, gridSize);
for (int index = 0; index < 4; ++index)
sortCandidate(neighbors[index], gridSize);
}
}
fragColor = uvec4(fluidJarEncodeId(nearestId), 0u, 0u);
}
@@ -0,0 +1,21 @@
#version 300 es
precision highp float;
precision highp usampler2D;
in vec2 v_texcoord;
uniform usampler2D fluidJarHistoryTex;
uniform vec2 fluidJarOldResolution;
uniform vec4 fluidJarHistoryTransform;
layout(location = 0) out uvec4 fragColor;
void main() {
vec2 oldPosition = gl_FragCoord.xy * fluidJarHistoryTransform.xy + fluidJarHistoryTransform.zw;
if (any(lessThan(oldPosition, vec2(0.0))) || any(greaterThanEqual(oldPosition, fluidJarOldResolution))) {
fragColor = uvec4(0u);
return;
}
fragColor = texelFetch(fluidJarHistoryTex, ivec2(oldPosition), 0);
}
@@ -0,0 +1,89 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
precision highp int;
precision highp usampler2D;
in vec2 v_texcoord;
uniform sampler2D fluidJarParticleTex;
uniform usampler2D fluidJarGraphTex;
uniform usampler2D fluidJarTrackingTex;
uniform sampler2D fluidJarVisualTex;
uniform vec2 fluidJarResolution;
uniform vec2 fluidJarGridSize;
uniform int fluidJarParticleCount;
uniform float fluidJarVisualResponse;
#include "fluidJar.glsl"
layout(location = 0) out vec4 fragColor;
const float FIELD_FALLOFF = 0.055;
vec2 samplePosition;
float fieldSum = 0.0;
float weightSum = 0.0;
vec2 velocitySum = vec2(0.0);
float materialSum = 0.0;
float shapedDistanceSquared(vec2 delta, vec2 shapeSeed) {
float distanceSquared = dot(delta, delta);
if (distanceSquared < 1e-6)
return 0.0;
vec2 axis = 2.0 * shapeSeed - 1.0;
axis *= inversesqrt(max(dot(axis, axis), 0.01));
vec2 direction = delta * inversesqrt(distanceSquared);
float along = dot(direction, axis);
float across = dot(direction, vec2(-axis.y, axis.x));
float thirdOrder = 4.0 * along * along * along - 3.0 * along;
float radialScale = clamp(1.0 + 0.10 * along + 0.06 * (2.0 * across * across - 1.0) + 0.035 * thirdOrder, 0.80, 1.20);
return distanceSquared / (radialScale * radialScale);
}
void addParticle(int id, ivec2 gridSize) {
if (id < 0 || id >= fluidJarParticleCount)
return;
vec4 state = texelFetch(fluidJarParticleTex, fluidJarAddress(id, 0, gridSize), 0);
vec4 material = texelFetch(fluidJarParticleTex, fluidJarAddress(id, 2, gridSize), 0);
vec2 delta = samplePosition - state.xy;
float contribution = exp(-FIELD_FALLOFF * shapedDistanceSquared(delta, material.zw));
fieldSum += contribution;
weightSum += contribution;
velocitySum += state.zw * contribution;
float materialValue = dot(2.0 * material - 1.0, vec4(0.40, 0.25, 0.22, 0.13));
materialSum += materialValue * contribution;
}
void main() {
ivec2 pixel = ivec2(gl_FragCoord.xy);
ivec2 gridSize = ivec2(fluidJarGridSize);
int id = fluidJarDecodeId(texelFetch(fluidJarTrackingTex, pixel, 0).rg);
vec4 previous = texelFetch(fluidJarVisualTex, pixel, 0);
if (id < 0 || id >= fluidJarParticleCount) {
fragColor = mix(previous, vec4(0.0), 0.65);
return;
}
samplePosition = vec2(pixel) + vec2(0.5);
addParticle(id, gridSize);
for (int direction = 0; direction < 4; ++direction) {
ivec4 neighbors = fluidJarLoadNeighbors(fluidJarGraphTex, id, direction, gridSize);
for (int index = 0; index < 4; ++index)
addParticle(neighbors[index], gridSize);
}
float field = 1.0 - exp(-fieldSum);
vec2 flow = weightSum > 1e-5 ? velocitySum / (1.5 * weightSum) : vec2(0.0);
float heterogeneity = weightSum > 1e-5 ? clamp(materialSum / weightSum, -1.0, 1.0) : 0.0;
float presence = smoothstep(0.03, 0.2, field);
vec4 current = vec4(clamp(flow, vec2(-1.0), vec2(1.0)) * presence, heterogeneity * presence, clamp(field, 0.0, 1.0));
vec3 response = vec3(1.0) - pow(vec3(0.55, 0.45, 0.15), vec3(max(fluidJarVisualResponse, 1.0)));
fragColor.rg = mix(previous.rg, current.rg, response.x);
fragColor.b = mix(previous.b, current.b, response.y);
fragColor.a = mix(previous.a, current.a, response.z);
}
+91
View File
@@ -0,0 +1,91 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform float glassRefraction;
uniform float glassSize;
uniform float glassRoughness;
uniform vec2 glassPosition;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "glassFinish.glsl"
layout(location = 0) out vec4 fragColor;
vec2 frostRandom(vec2 cell) {
return vec2(hash(cell + vec2(13.37, 71.91)), hash(cell + vec2(83.17, 29.53)));
}
vec2 frostWarp(vec2 position) {
const vec2 DIRECTION_1 = vec2(1.73, -1.21);
const vec2 DIRECTION_2 = vec2(1.11, 1.87);
const vec2 DIRECTION_3 = vec2(-2.19, 0.83);
vec2 warp = vec2(sin(dot(position, DIRECTION_1) + 0.7), cos(dot(position, DIRECTION_2) + 1.9));
warp += vec2(cos(dot(position, DIRECTION_3) + 2.8), sin(dot(position, DIRECTION_1 - DIRECTION_2) + 4.1)) * 0.45;
return warp * 0.16;
}
void frostCellular(vec2 position, out vec2 nearestOffset, out vec2 secondOffset) {
vec2 baseCell = floor(position);
float nearestDistance = 1e10;
float secondDistance = 1e10;
nearestOffset = vec2(0.0);
secondOffset = vec2(0.0);
for (int y = -1; y <= 1; ++y) {
for (int x = -1; x <= 1; ++x) {
vec2 cell = baseCell + vec2(float(x), float(y));
vec2 center = cell + mix(vec2(0.16), vec2(0.84), frostRandom(cell));
vec2 offset = position - center;
float distance = dot(offset, offset);
if (distance < nearestDistance) {
secondDistance = nearestDistance;
secondOffset = nearestOffset;
nearestDistance = distance;
nearestOffset = offset;
} else if (distance < secondDistance) {
secondDistance = distance;
secondOffset = offset;
}
}
}
}
vec2 frostGradient(vec2 position) {
position += frostWarp(position);
vec2 nearestOffset;
vec2 secondOffset;
frostCellular(position, nearestOffset, secondOffset);
float boundary = length(secondOffset) - length(nearestOffset);
float seamWidth = 0.028 + fwidth(boundary) * 1.5;
float seam = 1.0 - smoothstep(0.0, seamWidth, boundary);
vec2 seamDirection = secondOffset - nearestOffset;
seamDirection /= max(length(seamDirection), 0.0001);
vec2 grain = vec2(sin(dot(position, vec2(2.61, -1.43))), cos(dot(position, vec2(1.19, 2.37)))) * 0.09;
return nearestOffset * 0.34 + grain + seamDirection * seam * 0.55;
}
void main() {
vec2 position = (gl_FragCoord.xy - glassPosition) / glassSize;
vec2 gradient = frostGradient(position);
fragColor = glassFinish(gradient);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef ALLOW_INCLUDES
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
#endif
#include "blurFinish.glsl"
vec4 glassFinish(vec2 normal) {
normal /= max(1.0, length(normal));
vec2 uvStep = normal.x * dFdx(v_texcoord) + normal.y * dFdy(v_texcoord);
vec2 displacedUV = clamp(v_texcoord + glassRefraction * uvStep, vec2(0.0), vec2(1.0));
vec4 pixColor = texture(tex, displacedUV);
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
float emboss = dot(normal, LIGHT_DIRECTION);
pixColor.rgb *= 1.0 + emboss * glassRoughness * 0.12;
return blurFinish(pixColor, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+69
View File
@@ -0,0 +1,69 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform float hazeIntensity;
uniform float hazeIridescence;
uniform int hazeTransferFunction;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "cm_helpers.glsl"
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
const vec3 BT709_LUMA = vec3(0.2126, 0.7152, 0.0722);
const vec3 PEARL_COOL = vec3(0.55, 1.08, 1.35);
const vec3 PEARL_MID = vec3(1.28, 0.86, 1.30);
const vec3 PEARL_WARM = vec3(1.40, 0.92, 0.58);
vec3 pearlColor(float phase) {
float position = phase * 0.5 + 0.5;
vec3 color;
if (position < 0.5)
color = mix(PEARL_COOL, PEARL_MID, position * 2.0);
else
color = mix(PEARL_MID, PEARL_WARM, (position - 0.5) * 2.0);
return color / max(dot(color, BT709_LUMA), 0.001);
}
vec4 applyHaze(vec4 color) {
float intensity = clamp(hazeIntensity, 0.0, 1.0);
if (intensity <= 0.0 || color.a <= 0.001)
return color;
float alpha = color.a;
vec3 linearColor = toLinearRGB(max(color.rgb / alpha, vec3(0.0)), hazeTransferFunction);
float luminance = max(dot(linearColor, BT709_LUMA), 0.0);
float phase = clamp(dot(v_texcoord - vec2(0.5), vec2(1.28, -0.72)), -1.0, 1.0);
float iridescence = clamp(hazeIridescence, 0.0, 1.0);
vec3 spectralShift = luminance * (pearlColor(phase) - vec3(1.0)) * iridescence * 0.65;
float sheen = 1.0 + (1.0 - abs(phase)) * 0.08;
vec3 filmColor = max((linearColor + spectralShift) * sheen, vec3(0.0));
linearColor = mix(linearColor, filmColor, intensity);
return fromLinear(vec4(max(linearColor, vec3(0.0)) * alpha, alpha), hazeTransferFunction);
}
void main() {
vec4 color = applyHaze(texture(tex, v_texcoord));
fragColor = blurFinish(color, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
@@ -0,0 +1,44 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform float glassRefraction;
uniform float glassSize;
uniform float glassRoughness;
uniform vec2 glassPosition;
uniform float time;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "glassFinish.glsl"
layout(location = 0) out vec4 fragColor;
vec2 heatShimmerNormal(vec2 position) {
vec2 warpedPosition = position;
warpedPosition.x += sin(position.y * 0.73 - time) * 0.32;
warpedPosition.y += sin(position.x * 0.41 + time) * 0.12;
float broadPhase = warpedPosition.y * 1.15 + warpedPosition.x * 0.22 + time;
float detailPhase = warpedPosition.y * 2.37 - warpedPosition.x * 0.31 + time * 2.0;
float crossPhase = warpedPosition.x * 0.74 + warpedPosition.y * 0.41 - time;
return vec2(cos(broadPhase) * 0.46 + cos(detailPhase) * 0.22 + sin(crossPhase) * 0.09,
cos(crossPhase) * 0.09 + sin(detailPhase) * 0.05);
}
void main() {
vec2 position = (gl_FragCoord.xy - glassPosition) / glassSize;
fragColor = glassFinish(heatShimmerNormal(position));
}
+103
View File
@@ -0,0 +1,103 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform sampler2D sharpTex;
uniform float noise;
uniform float brightness;
uniform float glassRefraction;
uniform float glassSize;
uniform float glassRoughness;
uniform vec2 glassPosition;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
const float TAU = 6.28318530718;
float triangleWave(float phase) {
return 1.0 - abs(fract(phase) * 2.0 - 1.0);
}
float distanceToFold(float phase) {
phase = fract(phase);
return min(min(phase, 1.0 - phase), abs(phase - 0.5));
}
vec2 prismSurface(vec2 position, out float edge, out float spectrumPhase) {
const vec2 AXIS_A = vec2(1.0, 0.0);
const vec2 AXIS_B = vec2(-0.5, 0.8660254);
const vec2 AXIS_C = vec2(-0.5, -0.8660254);
vec3 phase = vec3(dot(position, AXIS_A), dot(position, AXIS_B), dot(position, AXIS_C));
vec3 slope = 1.0 - 2.0 * step(vec3(0.5), fract(phase));
vec2 normal = (slope.x * AXIS_A + slope.y * AXIS_B + slope.z * AXIS_C) * 0.46;
vec2 cell = floor(vec2(phase.x, phase.y));
vec2 variation = vec2(hash(cell + vec2(19.17, 73.41)), hash(cell + vec2(61.83, 11.29))) - 0.5;
normal += variation * 0.18;
float foldDistance = min(distanceToFold(phase.x), min(distanceToFold(phase.y), distanceToFold(phase.z)));
float antialias = max(fwidth(foldDistance), 0.001);
edge = 1.0 - smoothstep(antialias * 0.65, antialias * 2.8, foldDistance);
float height = triangleWave(phase.x) + triangleWave(phase.y) + triangleWave(phase.z);
spectrumPhase = fract(height * 0.19 + hash(cell) * 0.24);
return normal;
}
vec4 prismFinish(vec2 normal, float edge, float spectrumPhase) {
normal /= max(1.0, length(normal));
vec2 uvStep = normal.x * dFdx(v_texcoord) + normal.y * dFdy(v_texcoord);
vec2 refraction = glassRefraction * uvStep;
vec2 texSize = vec2(textureSize(tex, 0));
vec2 halfTexel = 0.5 / texSize;
vec2 minimumUV = halfTexel;
vec2 maximumUV = vec2(1.0) - halfTexel;
vec2 centerUV = clamp(v_texcoord + refraction * 0.72, minimumUV, maximumUV);
float dispersion = mix(0.12, 0.28, glassRoughness);
vec2 redUV = clamp(centerUV + refraction * dispersion, minimumUV, maximumUV);
vec2 blueUV = clamp(centerUV - refraction * dispersion, minimumUV, maximumUV);
vec4 blurred = texture(tex, centerUV);
vec4 sharpCenter = texture(sharpTex, centerUV);
vec3 dispersed = vec3(texture(sharpTex, redUV).r, sharpCenter.g, texture(sharpTex, blueUV).b);
float clarity = smoothstep(0.0, 6.0, glassRefraction) * mix(0.24, 0.42, glassRoughness);
vec4 pixColor = vec4(mix(blurred.rgb, dispersed, clarity), blurred.a);
const vec2 LIGHT_DIRECTION = vec2(-0.451219, 0.892413);
float emboss = dot(normal, LIGHT_DIRECTION);
float highlight = edge * glassRoughness * (0.018 + 0.055 * max(emboss, 0.0));
vec3 spectrum = 0.58 + 0.42 * cos(TAU * (spectrumPhase + vec3(0.0, 0.333333, 0.666667)));
pixColor.rgb *= 1.0 + emboss * glassRoughness * 0.1;
pixColor.rgb += spectrum * highlight * pixColor.a;
return blurFinish(pixColor, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
void main() {
vec2 position = (gl_FragCoord.xy - glassPosition) / glassSize;
float edge;
float spectrumPhase;
vec2 normal = prismSurface(position, edge, spectrumPhase);
fragColor = prismFinish(normal, edge, spectrumPhase);
}
+72
View File
@@ -0,0 +1,72 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
const int MAX_RIPPLE_IMPULSES = 256;
uniform int rippleCount;
uniform vec4 rippleImpulses[MAX_RIPPLE_IMPULSES];
uniform vec4 rippleParams;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
void main() {
vec2 texSize = vec2(textureSize(tex, 0));
vec2 position = v_texcoord * texSize;
vec2 displacement = vec2(0.0);
float duration = max(rippleParams.x, 0.001);
float maximumRadius = max(rippleParams.y, 1.0);
float waveWidth = max(rippleParams.z, 1.0);
float amplitude = max(rippleParams.w, 0.0);
for (int i = 0; i < MAX_RIPPLE_IMPULSES; ++i) {
if (i >= rippleCount)
break;
float progress = clamp(rippleImpulses[i].z / duration, 0.0, 1.0);
vec2 delta = position - rippleImpulses[i].xy;
float distance = length(delta);
vec2 direction = delta / max(distance, 1.0);
float waveRadius = maximumRadius * progress;
float signedDistance = distance - waveRadius;
float distanceFromWave = abs(signedDistance);
if (distanceFromWave >= waveWidth)
continue;
float envelope = 0.5 + 0.5 * cos(3.14159265359 * distanceFromWave / waveWidth);
float wave = cos(6.28318530718 * signedDistance / waveWidth);
displacement += direction * wave * envelope * (1.0 - progress);
}
float displacementLength = length(displacement);
if (displacementLength > 1.0)
displacement /= displacementLength;
displacement *= amplitude;
vec2 displacedUV = clamp(v_texcoord + displacement / texSize, vec2(0.0), vec2(1.0));
vec4 pixColor = texture(tex, displacedUV);
fragColor = blurFinish(pixColor, v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+57
View File
@@ -0,0 +1,57 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D tex;
uniform sampler2D waterStateTex;
uniform int waterEnabled;
uniform vec2 waterTexelSize;
uniform vec4 waterExtent;
uniform float waterRefraction;
uniform float noise;
uniform float brightness;
#include "defines.h"
#if USE_CM
uniform int sourceTF;
uniform int targetTF;
#include "CM.glsl"
#endif
#include "blurFinish.glsl"
layout(location = 0) out vec4 fragColor;
float waterHeight(vec2 uv) {
return texture(waterStateTex, clamp(uv, vec2(0.0), vec2(1.0))).r * 2.0 - 1.0;
}
void main() {
vec2 displacedUV = v_texcoord;
if (waterEnabled != 0) {
vec2 position = v_texcoord * vec2(textureSize(tex, 0));
vec2 waterUV = (position - waterExtent.xy) / waterExtent.zw;
if (all(greaterThanEqual(waterUV, vec2(0.0))) && all(lessThanEqual(waterUV, vec2(1.0)))) {
float left = waterHeight(waterUV - vec2(waterTexelSize.x, 0.0));
float right = waterHeight(waterUV + vec2(waterTexelSize.x, 0.0));
float up = waterHeight(waterUV - vec2(0.0, waterTexelSize.y));
float down = waterHeight(waterUV + vec2(0.0, waterTexelSize.y));
vec2 gradient = vec2(left - right, up - down);
float gradientLength = length(gradient);
if (gradientLength > 1.0)
gradient /= gradientLength;
vec2 texSize = vec2(textureSize(tex, 0));
displacedUV = clamp(v_texcoord + gradient * waterRefraction / texSize, vec2(0.0), vec2(1.0));
}
}
fragColor = blurFinish(texture(tex, displacedUV), v_texcoord, noise, brightness
#if USE_CM
,
sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange
#endif
);
}
+60
View File
@@ -0,0 +1,60 @@
#version 300 es
#define ALLOW_INCLUDES
#extension GL_ARB_shading_language_include : enable
precision highp float;
in vec2 v_texcoord;
uniform sampler2D waterStateTex;
uniform vec2 waterTexelSize;
uniform vec4 waterParams;
const int MAX_WATER_IMPULSES = 16;
uniform int waterImpulseCount;
uniform vec4 waterImpulses[MAX_WATER_IMPULSES];
layout(location = 0) out vec4 fragColor;
float decodeState(float value) {
return value * 2.0 - 1.0;
}
float encodeState(float value) {
return clamp(value * 0.5 + 0.5, 0.0, 1.0);
}
void main() {
vec2 state = texture(waterStateTex, v_texcoord).rg;
float height = decodeState(state.r);
float velocity = decodeState(state.g);
float left = decodeState(texture(waterStateTex, v_texcoord - vec2(waterTexelSize.x, 0.0)).r);
float right = decodeState(texture(waterStateTex, v_texcoord + vec2(waterTexelSize.x, 0.0)).r);
float up = decodeState(texture(waterStateTex, v_texcoord - vec2(0.0, waterTexelSize.y)).r);
float down = decodeState(texture(waterStateTex, v_texcoord + vec2(0.0, waterTexelSize.y)).r);
float frameStep = clamp(waterParams.x * 60.0, 0.0, 3.0);
float propagation = waterParams.y * 0.24;
float damping = pow(waterParams.z, frameStep);
float laplacian = left + right + up + down - 4.0 * height;
velocity = (velocity + laplacian * propagation * frameStep) * damping;
height += velocity * frameStep;
float edgeDistance = min(min(v_texcoord.x, 1.0 - v_texcoord.x), min(v_texcoord.y, 1.0 - v_texcoord.y));
float edgeFade = smoothstep(0.0, 0.04, edgeDistance);
velocity *= edgeFade;
height *= mix(0.9, 1.0, edgeFade);
for (int i = 0; i < MAX_WATER_IMPULSES; ++i) {
if (i >= waterImpulseCount)
break;
vec2 delta = v_texcoord - waterImpulses[i].xy;
float radius = max(waterImpulses[i].z, 0.0001);
float influence = exp(-dot(delta, delta) / (radius * radius));
height += influence * waterImpulses[i].w;
}
fragColor = vec4(encodeState(height), encodeState(velocity), 0.0, 1.0);
}
+255
View File
@@ -0,0 +1,255 @@
#include <config/values/ConfigValues.hpp>
#include <render/blur/Provider.hpp>
#include <gtest/gtest.h>
#include <array>
#include <ranges>
#include <string_view>
using namespace Config::Values;
using namespace Render;
TEST(Config, blurVariantsMatchRendererTypes) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [](const auto& value) { return std::string_view{value->name()} == "decoration:blur:variant"; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto INT_VALUE = dynamicPointerCast<CIntValue>(*VALUE);
ASSERT_TRUE(INT_VALUE);
ASSERT_TRUE(INT_VALUE->m_min.has_value());
ASSERT_TRUE(INT_VALUE->m_max.has_value());
ASSERT_TRUE(INT_VALUE->m_map.has_value());
EXPECT_EQ(INT_VALUE->defaultVal(), sc<Config::INTEGER>(eBlurType::BLUR_DUAL_KAWASE));
EXPECT_EQ(*INT_VALUE->m_min, sc<Config::INTEGER>(eBlurType::BLUR_DUAL_KAWASE));
EXPECT_EQ(*INT_VALUE->m_max, sc<Config::INTEGER>(eBlurType::BLUR_HAZE));
const auto& MAP = *INT_VALUE->m_map;
EXPECT_EQ(MAP.size(), 11);
EXPECT_EQ(MAP.at("kawase"), sc<Config::INTEGER>(eBlurType::BLUR_DUAL_KAWASE));
EXPECT_EQ(MAP.at("frost"), sc<Config::INTEGER>(eBlurType::BLUR_FROST));
EXPECT_FALSE(MAP.contains("fluted"));
EXPECT_FALSE(MAP.contains("hammered"));
EXPECT_EQ(MAP.at("ripple"), sc<Config::INTEGER>(eBlurType::BLUR_RIPPLE));
EXPECT_EQ(MAP.at("drops"), sc<Config::INTEGER>(eBlurType::BLUR_DROPS));
EXPECT_EQ(MAP.at("water"), sc<Config::INTEGER>(eBlurType::BLUR_WATER));
EXPECT_EQ(MAP.at("fluid_jar"), sc<Config::INTEGER>(eBlurType::BLUR_FLUID_JAR));
EXPECT_EQ(MAP.at("prism"), sc<Config::INTEGER>(eBlurType::BLUR_PRISM));
EXPECT_EQ(MAP.at("heat_shimmer"), sc<Config::INTEGER>(eBlurType::BLUR_HEAT_SHIMMER));
EXPECT_EQ(MAP.at("acrylic"), sc<Config::INTEGER>(eBlurType::BLUR_ACRYLIC));
EXPECT_EQ(MAP.at("aurora"), sc<Config::INTEGER>(eBlurType::BLUR_AURORA));
EXPECT_EQ(MAP.at("haze"), sc<Config::INTEGER>(eBlurType::BLUR_HAZE));
EXPECT_EQ(INT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
TEST(Config, dropsAnimationSpeedIsBounded) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [](const auto& value) { return std::string_view{value->name()} == "decoration:blur:drops:speed"; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), 3.F);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, 0.F);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, 10.F);
}
TEST(Config, heatShimmerAnimationSpeedIsBounded) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [](const auto& value) { return std::string_view{value->name()} == "decoration:blur:heat_shimmer:speed"; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), 1.F);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, 0.F);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, 10.F);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
TEST(Config, auroraSettingsAreBounded) {
struct SAuroraSetting {
std::string_view name;
float defaultValue;
float min;
float max;
};
constexpr std::array SETTINGS = {
SAuroraSetting{"decoration:blur:aurora:speed", 1.F, 0.F, 10.F},
SAuroraSetting{"decoration:blur:aurora:intensity", 0.35F, 0.F, 1.F},
};
for (const auto& setting : SETTINGS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&setting](const auto& value) { return std::string_view{value->name()} == setting.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), setting.defaultValue);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, setting.min);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, setting.max);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
struct SAuroraColor {
std::string_view name;
int64_t defaultValue;
};
constexpr std::array COLORS = {
SAuroraColor{"decoration:blur:aurora:color1", 0x29F0A0FF},
SAuroraColor{"decoration:blur:aurora:color2", 0x7A4DFFFF},
};
for (const auto& color : COLORS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&color](const auto& value) { return std::string_view{value->name()} == color.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto COLOR_VALUE = dynamicPointerCast<CColorValue>(*VALUE);
ASSERT_TRUE(COLOR_VALUE);
EXPECT_EQ(COLOR_VALUE->defaultVal(), color.defaultValue);
EXPECT_EQ(COLOR_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
}
TEST(Config, hazeSettingsAreBounded) {
struct SHazeSetting {
std::string_view name;
float defaultValue;
};
constexpr std::array SETTINGS = {
SHazeSetting{"decoration:blur:haze:intensity", 0.35F},
SHazeSetting{"decoration:blur:haze:iridescence", 0.7F},
};
for (const auto& setting : SETTINGS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&setting](const auto& value) { return std::string_view{value->name()} == setting.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), setting.defaultValue);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, 0.F);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, 1.F);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
}
TEST(Config, acrylicSettingsAreBounded) {
struct SAcrylicSetting {
std::string_view name;
float defaultValue;
float min;
float max;
};
constexpr std::array SETTINGS = {
SAcrylicSetting{"decoration:blur:acrylic:refraction", 24.F, 0.F, 48.F},
SAcrylicSetting{"decoration:blur:acrylic:bulb", 48.F, 4.F, 256.F},
SAcrylicSetting{"decoration:blur:acrylic:clarity", 0.82F, 0.F, 1.F},
SAcrylicSetting{"decoration:blur:acrylic:aberration", 0.025F, 0.F, 0.25F},
};
for (const auto& setting : SETTINGS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&setting](const auto& value) { return std::string_view{value->name()} == setting.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), setting.defaultValue);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, setting.min);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, setting.max);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
const auto TINT = std::ranges::find_if(CONFIG_VALUES, [](const auto& value) { return std::string_view{value->name()} == "decoration:blur:acrylic:tint"; });
ASSERT_NE(TINT, CONFIG_VALUES.end());
const auto COLOR_VALUE = dynamicPointerCast<CColorValue>(*TINT);
ASSERT_TRUE(COLOR_VALUE);
EXPECT_EQ(COLOR_VALUE->defaultVal(), 0x14EEF5FF);
EXPECT_EQ(COLOR_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
TEST(Config, waterSettingsAreBounded) {
struct SWaterSetting {
std::string_view name;
float defaultValue;
float min;
float max;
};
constexpr std::array SETTINGS = {
SWaterSetting{"decoration:blur:water:strength", 32.F, 0.F, 32.F}, SWaterSetting{"decoration:blur:water:radius", 20.F, 1.F, 1000.F},
SWaterSetting{"decoration:blur:water:speed", 0.76F, 0.F, 10.F}, SWaterSetting{"decoration:blur:water:damping", 0.95F, 0.F, 1.F},
SWaterSetting{"decoration:blur:water:duration", 12.F, 0.5F, 60.F},
};
for (const auto& setting : SETTINGS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&setting](const auto& value) { return std::string_view{value->name()} == setting.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), setting.defaultValue);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, setting.min);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, setting.max);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
}
TEST(Config, fluidJarSettingsAreBounded) {
const auto COLOR = std::ranges::find_if(CONFIG_VALUES, [](const auto& value) { return std::string_view{value->name()} == "decoration:blur:fluid_jar:color"; });
ASSERT_NE(COLOR, CONFIG_VALUES.end());
const auto COLOR_VALUE = dynamicPointerCast<CColorValue>(*COLOR);
ASSERT_TRUE(COLOR_VALUE);
EXPECT_EQ(COLOR_VALUE->defaultVal(), 0xCC3399FF);
EXPECT_EQ(COLOR_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
struct SFluidJarSetting {
std::string_view name;
float defaultValue;
float min;
float max;
};
constexpr std::array SETTINGS = {
SFluidJarSetting{"decoration:blur:fluid_jar:speed", 3.7F, 0.F, 10.F}, SFluidJarSetting{"decoration:blur:fluid_jar:fill_amount", 0.5F, 0.F, 1.F},
SFluidJarSetting{"decoration:blur:fluid_jar:mass", 1.4F, 0.1F, 10.F}, SFluidJarSetting{"decoration:blur:fluid_jar:precision", 2.F, 0.5F, 8.F},
SFluidJarSetting{"decoration:blur:fluid_jar:turbulence", 1.2F, 0.F, 5.F}, SFluidJarSetting{"decoration:blur:fluid_jar:distortion", 8.F, 0.F, 10.F},
};
for (const auto& setting : SETTINGS) {
const auto VALUE = std::ranges::find_if(CONFIG_VALUES, [&setting](const auto& value) { return std::string_view{value->name()} == setting.name; });
ASSERT_NE(VALUE, CONFIG_VALUES.end());
const auto FLOAT_VALUE = dynamicPointerCast<CFloatValue>(*VALUE);
ASSERT_TRUE(FLOAT_VALUE);
ASSERT_TRUE(FLOAT_VALUE->m_min.has_value());
ASSERT_TRUE(FLOAT_VALUE->m_max.has_value());
EXPECT_FLOAT_EQ(FLOAT_VALUE->defaultVal(), setting.defaultValue);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_min, setting.min);
EXPECT_FLOAT_EQ(*FLOAT_VALUE->m_max, setting.max);
EXPECT_EQ(FLOAT_VALUE->refreshBits(), Config::Supplementary::REFRESH_BLUR_FB);
}
}
+103
View File
@@ -0,0 +1,103 @@
#include <render/pass/BackdropScopePassElement.hpp>
#include <render/pass/TexPassElement.hpp>
#include <render/pass/TransformedWindowPassElement.hpp>
#include <gtest/gtest.h>
TEST(BackdropScopePassElement, MarkersAreBalancedPassMetadata) {
const auto scope = makeShared<SBackdropScope>();
CBackdropScopePassElement begin{CBackdropScopePassElement::eAction::BEGIN, scope};
CBackdropScopePassElement end{CBackdropScopePassElement::eAction::END, scope};
EXPECT_EQ(begin.action(), CBackdropScopePassElement::eAction::BEGIN);
EXPECT_EQ(end.action(), CBackdropScopePassElement::eAction::END);
EXPECT_EQ(begin.scope(), scope);
EXPECT_EQ(end.scope(), scope);
EXPECT_EQ(begin.type(), EK_BACKDROP_SCOPE);
EXPECT_EQ(end.type(), EK_BACKDROP_SCOPE);
}
TEST(BackdropScopePassElement, MarkersSurviveSimplificationWithoutRequestingBlur) {
const auto scope = makeShared<SBackdropScope>();
CBackdropScopePassElement marker{CBackdropScopePassElement::eAction::BEGIN, scope};
EXPECT_TRUE(marker.undiscardable());
EXPECT_FALSE(marker.needsLiveBlur());
EXPECT_FALSE(marker.needsPrecomputeBlur());
EXPECT_FALSE(marker.disableSimplification());
EXPECT_FALSE(marker.boundingBox().has_value());
EXPECT_TRUE(marker.opaqueRegion().empty());
}
TEST(BackdropScopePlanner, ActivatesOnlyInnermostScopeAndClipsDamage) {
CBackdropScopePlanner planner;
const auto outer = makeShared<SBackdropScope>();
const auto inner = makeShared<SBackdropScope>();
planner.begin(outer);
planner.begin(inner);
planner.addLiveBlur(CRegion{80, 80, 40, 40});
planner.end(inner, CBox{0, 0, 100, 100});
planner.end(outer, CBox{0, 0, 100, 100});
EXPECT_FALSE(outer->required);
EXPECT_TRUE(outer->damage.empty());
EXPECT_TRUE(inner->required);
EXPECT_EQ(inner->damage.getExtents(), CBox(80, 80, 20, 20));
EXPECT_TRUE(planner.empty());
}
TEST(BackdropScopePlanner, UnionsLiveBlurDamageWithinScope) {
CBackdropScopePlanner planner;
const auto scope = makeShared<SBackdropScope>();
planner.begin(scope);
planner.addLiveBlur(CRegion{10, 20, 30, 40});
planner.addLiveBlur(CRegion{50, 60, 20, 10});
planner.end(scope, CBox{0, 0, 100, 100});
EXPECT_TRUE(scope->required);
EXPECT_EQ(scope->damage.getExtents(), CBox(10, 20, 60, 50));
}
TEST(BackdropScopePlanner, TransformedWindowReportsNestedLiveBlur) {
auto nestedPass = makeUnique<Render::CRenderPass>();
nestedPass->add(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.blur = true,
.blockBlurOptimization = true,
}));
CTransformedWindowPassElement transformed{CTransformedWindowPassElement::SData{.pass = std::move(nestedPass)}};
EXPECT_TRUE(transformed.needsLiveBlur());
}
TEST(BackdropScopePlanner, TransformedWindowReportsNestedPrecomputedBlur) {
auto nestedPass = makeUnique<Render::CRenderPass>();
nestedPass->add(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.blur = true,
.liveBlurOverride = false,
}));
CTransformedWindowPassElement transformed{CTransformedWindowPassElement::SData{.pass = std::move(nestedPass)}};
EXPECT_TRUE(transformed.needsPrecomputeBlur());
}
TEST(BackdropScopePlanner, TransformedWindowPreservesLiveBlurMode) {
CTransformedWindowPassElement transformed{CTransformedWindowPassElement::SData{
.blur = true,
.blurUsesLive = true,
}};
EXPECT_TRUE(transformed.needsLiveBlur());
EXPECT_FALSE(transformed.needsPrecomputeBlur());
}
TEST(BackdropScopePlanner, TransformedWindowPreservesPrecomputedBlurMode) {
CTransformedWindowPassElement transformed{CTransformedWindowPassElement::SData{
.blur = true,
.blurUsesLive = false,
}};
EXPECT_FALSE(transformed.needsLiveBlur());
EXPECT_TRUE(transformed.needsPrecomputeBlur());
}
+291
View File
@@ -0,0 +1,291 @@
#include <render/gl/blur/Acrylic.hpp>
#include <render/gl/blur/Aurora.hpp>
#include <render/gl/blur/Kawase.hpp>
#include <render/gl/blur/Glass.hpp>
#include <render/gl/blur/FluidJar.hpp>
#include <render/gl/blur/HeatShimmer.hpp>
#include <render/gl/blur/Haze.hpp>
#include <render/gl/blur/Prism.hpp>
#include <render/gl/blur/Ripple.hpp>
#include <render/gl/blur/Water.hpp>
#include <render/ShaderLoader.hpp>
#include <gtest/gtest.h>
using namespace Render::GL;
TEST(BlurMaterial, DefaultUsesPlainFinish) {
const CDefaultBlurMaterial material;
const auto requirements = material.requirements();
EXPECT_EQ(material.type(), Render::eBlurType::BLUR_DUAL_KAWASE);
EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_BLURFINISH);
EXPECT_FALSE(requirements.preparedInput);
EXPECT_FALSE(requirements.liveBlur);
EXPECT_FALSE(material.isAnimated());
EXPECT_EQ(material.blurSizeForDamage(100), 40);
EXPECT_FLOAT_EQ(material.sampleRadius(), 0.F);
}
TEST(BlurMaterial, GlassCapabilitiesAreConfiguredByMaterial) {
const CGlassBlurMaterial frost(Render::eBlurType::BLUR_FROST, Render::SH_FRAG_FROSTFINISH);
const auto frostRequirements = frost.requirements();
EXPECT_EQ(frost.type(), Render::eBlurType::BLUR_FROST);
EXPECT_EQ(frostRequirements.finishFragment, Render::SH_FRAG_FROSTFINISH);
EXPECT_FALSE(frostRequirements.preparedInput);
const CPrismBlurMaterial prism;
const auto prismRequirements = prism.requirements();
EXPECT_EQ(prism.type(), Render::eBlurType::BLUR_PRISM);
EXPECT_EQ(prismRequirements.finishFragment, Render::SH_FRAG_PRISMFINISH);
EXPECT_TRUE(prismRequirements.preparedInput);
}
TEST(BlurMaterial, HeatShimmerUsesAnimatedGlassFinish) {
const CHeatShimmerBlurMaterial heatShimmer;
const auto requirements = heatShimmer.requirements();
EXPECT_EQ(heatShimmer.type(), Render::eBlurType::BLUR_HEAT_SHIMMER);
EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_HEATSHIMMERFINISH);
EXPECT_FALSE(requirements.preparedInput);
EXPECT_FALSE(requirements.liveBlur);
}
TEST(BlurMaterial, AuroraUsesAnimatedGlassFinish) {
const CAuroraBlurMaterial aurora;
const auto requirements = aurora.requirements();
EXPECT_EQ(aurora.type(), Render::eBlurType::BLUR_AURORA);
EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_AURORAFINISH);
EXPECT_FALSE(requirements.preparedInput);
EXPECT_FALSE(requirements.liveBlur);
}
TEST(BlurMaterial, HazeUsesStaticPearlescentFinish) {
const CHazeBlurMaterial haze;
const auto requirements = haze.requirements();
EXPECT_EQ(haze.type(), Render::eBlurType::BLUR_HAZE);
EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_HAZEFINISH);
EXPECT_FALSE(requirements.preparedInput);
EXPECT_FALSE(requirements.liveBlur);
EXPECT_FALSE(haze.isAnimated());
EXPECT_EQ(haze.blurSizeForDamage(100), 40);
EXPECT_FLOAT_EQ(haze.sampleRadius(), 0.F);
}
TEST(BlurMaterial, AcrylicUsesPreparedLiveFinish) {
const CAcrylicBlurMaterial acrylic;
const auto requirements = acrylic.requirements();
EXPECT_EQ(acrylic.type(), Render::eBlurType::BLUR_ACRYLIC);
EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_ACRYLICFINISH);
EXPECT_TRUE(requirements.preparedInput);
EXPECT_TRUE(requirements.liveBlur);
EXPECT_FALSE(acrylic.isAnimated());
}
TEST(BlurDamage, DualKawaseUsesOperationalMinimums) {
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(0, 0), 2.F);
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(-10, -10), 2.F);
}
TEST(BlurDamage, DualKawaseCalculatesConfiguredRadius) {
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(8, 1), 16.F);
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(8, 2), 48.F);
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(12, 3), 168.F);
}
TEST(BlurDamage, DualKawaseUsesOperationalMaximums) {
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(40, 8), 20400.F);
EXPECT_FLOAT_EQ(dualKawaseDamageRadius(100, 10), 51000.F);
}
TEST(BlurDamage, GlassIncludesRefractionReach) {
EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, 3.F), 19.F);
EXPECT_FLOAT_EQ(glassDamageRadius(12, 3, 4.25F), 173.F);
}
TEST(BlurDamage, GlassClampsRefractionReach) {
EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, -1.F), 16.F);
EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, 100.F), 36.F);
}
TEST(BlurDamage, AcrylicIncludesFilteredRefractionReach) {
EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, 3.F), 20.F);
EXPECT_FLOAT_EQ(acrylicDamageRadius(12, 3, 4.25F), 174.F);
}
TEST(BlurDamage, AcrylicClampsRefractionReach) {
EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, -1.F), 16.F);
EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, 100.F), 65.F);
}
TEST(BlurDamage, RippleIncludesBoundedDisplacement) {
EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, 6.F), 22.F);
EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, -1.F), 16.F);
EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, 100.F), 48.F);
}
TEST(BlurDamage, RippleOutputReachIncludesWaveWidth) {
EXPECT_FLOAT_EQ(rippleOutputReach(180.F, 24.F), 204.F);
EXPECT_FLOAT_EQ(rippleOutputReach(10.25F, 2.25F), 13.F);
EXPECT_FLOAT_EQ(rippleOutputReach(-1.F, -1.F), 0.F);
}
TEST(BlurDamage, WaterIncludesBoundedDisplacement) {
EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, 6.F), 22.F);
EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, -1.F), 16.F);
EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, 100.F), 48.F);
}
TEST(FluidJar, SimulationSizePreservesAspectAndBoundsParticles) {
const auto wide = fluidJarSimulationSize({1920, 1080});
EXPECT_EQ(wide, Vector2D(256, 144));
EXPECT_LE(fluidJarParticleCapacity(wide), 2048);
const auto square = fluidJarSimulationSize({1000, 1000});
EXPECT_EQ(square, Vector2D(250, 250));
EXPECT_LE(fluidJarParticleCapacity(square), 2048);
}
TEST(FluidJar, DamageIncludesBoundedRefraction) {
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1), 24.F);
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 2), 56.F);
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 0.F), 16.F);
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 2.F), 32.F);
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 4.F), 48.F);
EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 10.F), 96.F);
}
TEST(FluidJar, OutputTransformsMapFramebufferToLogicalCoordinates) {
const std::array<SFluidJarOutputTransform, 8> expected = {
SFluidJarOutputTransform{.xAxis = {1, 0}, .yAxis = {0, 1}, .offset = {0, 0}}, SFluidJarOutputTransform{.xAxis = {0, -1}, .yAxis = {1, 0}, .offset = {0, 1}},
SFluidJarOutputTransform{.xAxis = {-1, 0}, .yAxis = {0, -1}, .offset = {1, 1}}, SFluidJarOutputTransform{.xAxis = {0, 1}, .yAxis = {-1, 0}, .offset = {1, 0}},
SFluidJarOutputTransform{.xAxis = {-1, 0}, .yAxis = {0, 1}, .offset = {1, 0}}, SFluidJarOutputTransform{.xAxis = {0, 1}, .yAxis = {1, 0}, .offset = {0, 0}},
SFluidJarOutputTransform{.xAxis = {1, 0}, .yAxis = {0, -1}, .offset = {0, 1}}, SFluidJarOutputTransform{.xAxis = {0, -1}, .yAxis = {-1, 0}, .offset = {1, 1}},
};
for (size_t i = 0; i < expected.size(); ++i) {
const auto transform = fluidJarOutputTransform(sc<eTransform>(i));
EXPECT_EQ(transform.xAxis, expected[i].xAxis);
EXPECT_EQ(transform.yAxis, expected[i].yAxis);
EXPECT_EQ(transform.offset, expected[i].offset);
}
}
TEST(FluidJar, OutputTransformsKeepTheFloorAtLogicalBottom) {
const std::array<Vector2D, 8> outputBottomCenters = {
Vector2D{0.5, 1.0}, Vector2D{0.0, 0.5}, Vector2D{0.5, 0.0}, Vector2D{1.0, 0.5}, Vector2D{0.5, 1.0}, Vector2D{1.0, 0.5}, Vector2D{0.5, 0.0}, Vector2D{0.0, 0.5},
};
for (size_t i = 0; i < outputBottomCenters.size(); ++i) {
const auto transform = fluidJarOutputTransform(sc<eTransform>(i));
const auto point = outputBottomCenters[i];
const auto logical = transform.xAxis * point.x + transform.yAxis * point.y + transform.offset;
EXPECT_EQ(logical, Vector2D(0.5, 1.0));
}
}
TEST(FluidJar, OutputTransformVectorsRoundTrip) {
constexpr Vector2D LOGICAL_VECTOR = {0.25, -0.75};
for (int i = 0; i < 8; ++i) {
const auto transform = fluidJarOutputTransform(sc<eTransform>(i));
const Vector2D outputVector = {
transform.xAxis.x * LOGICAL_VECTOR.x + transform.xAxis.y * LOGICAL_VECTOR.y,
transform.yAxis.x * LOGICAL_VECTOR.x + transform.yAxis.y * LOGICAL_VECTOR.y,
};
const auto logicalVector = transform.xAxis * outputVector.x + transform.yAxis * outputVector.y;
EXPECT_EQ(logicalVector, LOGICAL_VECTOR);
}
}
TEST(FluidJar, SimulationSizeRejectsEmptyExtents) {
EXPECT_EQ(fluidJarSimulationSize({0, 100}), Vector2D());
EXPECT_EQ(fluidJarSimulationSize({100, 0}), Vector2D());
}
TEST(FluidJar, PrecisionScalesSimulationAndClamps) {
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 0.5F), Vector2D(128, 72));
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 2.F), Vector2D(512, 288));
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 4.F), Vector2D(1024, 576));
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 8.F), Vector2D(2048, 1152));
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 0.1F), Vector2D(128, 72));
EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 20.F), Vector2D(2048, 1152));
}
TEST(FluidJar, MaximumPrecisionBoundsParticleCapacity) {
const auto size = fluidJarSimulationSize({4096, 4096}, 8.F);
EXPECT_EQ(size, Vector2D(2048, 2048));
EXPECT_EQ(fluidJarParticleCapacity(size), 131072);
}
TEST(FluidJar, FillAmountControlsInitialParticles) {
const Vector2D size = {256, 144};
EXPECT_EQ(fluidJarParticleCapacity(size), 1152);
EXPECT_EQ(fluidJarInitialParticleCount(size, 0.4F), 460);
EXPECT_EQ(fluidJarInitialParticleCount(size, -1.F), 0);
EXPECT_EQ(fluidJarInitialParticleCount(size, 2.F), 1152);
}
TEST(FluidJar, ResizePreservesParticles) {
EXPECT_EQ(fluidJarResizedParticleCount(460, {256, 144}), 460);
EXPECT_EQ(fluidJarResizedParticleCount(460, {64, 64}), 460);
EXPECT_EQ(fluidJarResizedParticleCount(64, {256, 144}), 64);
}
TEST(FluidJar, GeometryTransformPreservesWorldPositionDuringMove) {
const auto transform = fluidJarGeometryTransform({100, 200, 800, 600}, {140, 220, 800, 600}, {200, 150}, {200, 150});
EXPECT_EQ(transform.positionScale, Vector2D(1, 1));
EXPECT_EQ(transform.positionOffset, Vector2D(-10, 5));
EXPECT_EQ(transform.velocityScale, Vector2D(1, 1));
}
TEST(FluidJar, GeometryTransformPreservesStationaryResizeEdges) {
const auto right = fluidJarGeometryTransform({0, 0, 800, 600}, {0, 0, 1000, 600}, {200, 150}, {250, 150});
EXPECT_EQ(right.positionScale, Vector2D(1, 1));
EXPECT_EQ(right.positionOffset, Vector2D(0, 0));
const auto left = fluidJarGeometryTransform({0, 0, 800, 600}, {-200, 0, 1000, 600}, {200, 150}, {250, 150});
EXPECT_EQ(left.positionScale, Vector2D(1, 1));
EXPECT_EQ(left.positionOffset, Vector2D(50, 0));
const auto bottom = fluidJarGeometryTransform({0, 0, 800, 600}, {0, 0, 800, 800}, {200, 150}, {200, 200});
EXPECT_EQ(bottom.positionScale, Vector2D(1, 1));
EXPECT_EQ(bottom.positionOffset, Vector2D(0, 50));
}
TEST(FluidJar, DiscontinuousGeometryFollowsContainer) {
const auto transform = fluidJarGeometryTransform({0, 0, 800, 600}, {1200, 400, 1000, 800}, {200, 150}, {250, 200}, false);
EXPECT_EQ(transform.positionScale, Vector2D(1.25, 4.0 / 3.0));
EXPECT_EQ(transform.positionOffset, Vector2D(0, 0));
EXPECT_EQ(transform.velocityScale, Vector2D(0, 0));
}
TEST(FluidJar, WallVelocityUsesSimulationCoordinates) {
const auto moved = fluidJarWallVelocities({0, 0, 800, 600}, {6, 6, 800, 600}, {200, 150}, 1.F / 60.F);
EXPECT_FLOAT_EQ(moved[0], 0.5F);
EXPECT_FLOAT_EQ(moved[1], 0.5F);
EXPECT_FLOAT_EQ(moved[2], -0.5F);
const auto fasterSimulation = fluidJarWallVelocities({0, 0, 800, 600}, {6, 6, 800, 600}, {200, 150}, 1.F / 60.F, 2.F);
EXPECT_FLOAT_EQ(fasterSimulation[0], 0.25F);
EXPECT_FLOAT_EQ(fasterSimulation[1], 0.25F);
EXPECT_FLOAT_EQ(fasterSimulation[2], -0.25F);
const auto resized = fluidJarWallVelocities({0, 0, 800, 600}, {0, 0, 806, 600}, {200, 150}, 1.F / 60.F);
EXPECT_FLOAT_EQ(resized[0], 0.F);
EXPECT_NEAR(resized[1], 6.F * (200.F / 806.F) / 3.F, 0.0001F);
EXPECT_FLOAT_EQ(resized[2], 0.F);
}
TEST(FluidJar, WallVelocityRejectsInvalidIntervalsAndClampsSpikes) {
EXPECT_EQ(fluidJarWallVelocities({0, 0, 800, 600}, {10, 0, 800, 600}, {200, 150}, 0.F), (std::array<float, 4>{}));
const auto velocity = fluidJarWallVelocities({0, 0, 800, 600}, {1000, 0, 800, 600}, {200, 150}, 1.F / 60.F);
EXPECT_FLOAT_EQ(velocity[0], 1.25F);
EXPECT_FLOAT_EQ(velocity[1], 1.25F);
EXPECT_EQ(fluidJarWallVelocities({0, 0, 800, 600}, {10, 0, 800, 600}, {200, 150}, 1.F / 60.F, 0.F), (std::array<float, 4>{}));
}
+40
View File
@@ -0,0 +1,40 @@
#include <render/pass/TexPassElement.hpp>
#include <gtest/gtest.h>
TEST(TexPassElement, ReportsNoBlur) {
CTexPassElement element{CTexPassElement::SRenderData{}};
EXPECT_FALSE(element.needsLiveBlur());
EXPECT_FALSE(element.needsPrecomputeBlur());
}
TEST(TexPassElement, ReportsExplicitLiveBlur) {
CTexPassElement element{CTexPassElement::SRenderData{
.blur = true,
.blockBlurOptimization = true,
}};
EXPECT_TRUE(element.needsLiveBlur());
EXPECT_FALSE(element.needsPrecomputeBlur());
}
TEST(TexPassElement, LiveBlurOverrideForcesLiveBlur) {
CTexPassElement element{CTexPassElement::SRenderData{
.blur = true,
.liveBlurOverride = true,
}};
EXPECT_TRUE(element.needsLiveBlur());
EXPECT_FALSE(element.needsPrecomputeBlur());
}
TEST(TexPassElement, LiveBlurOverrideForcesPrecomputedBlur) {
CTexPassElement element{CTexPassElement::SRenderData{
.blur = true,
.liveBlurOverride = false,
}};
EXPECT_FALSE(element.needsLiveBlur());
EXPECT_TRUE(element.needsPrecomputeBlur());
}