mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 02:24:14 -05:00
renderer/gl: implement swappable blur variants (#15661)
Adds multiple variants to the blur shader
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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}),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace Desktop::View {
|
||||
void onReposition();
|
||||
|
||||
void recheckTree();
|
||||
bool shouldBlur() const;
|
||||
|
||||
bool inert() const;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -802,7 +823,10 @@ void IElementRenderer::drawTransformedWindow(WP<CTransformedWindowPassElement> e
|
||||
if (element->m_data.blur && blurAlphaMatte) {
|
||||
data.blur = true;
|
||||
data.forceBlurBlend = true;
|
||||
data.blockBlurOptimization = 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;
|
||||
|
||||
+108
-4
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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()!");
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Config {
|
||||
}
|
||||
|
||||
namespace Render::GL {
|
||||
class CDualKawaseBlurProvider;
|
||||
|
||||
CBox resolveBlurUV(const CBox& destinationBox, const Vector2D& textureSize);
|
||||
|
||||
@@ -158,6 +159,8 @@ namespace Render::GL {
|
||||
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,9 +364,6 @@ 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,
|
||||
@@ -385,6 +384,7 @@ namespace Render::GL {
|
||||
friend class CTexPassElement;
|
||||
friend class CPreBlurElement;
|
||||
friend class CSurfacePassElement;
|
||||
friend class CDualKawaseBlurProvider;
|
||||
};
|
||||
|
||||
inline UP<CHyprOpenGLImpl> g_pHyprOpenGL;
|
||||
|
||||
+87
-54
@@ -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) {
|
||||
@@ -646,6 +647,10 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
|
||||
UP<CRenderPass> transformedPass;
|
||||
UP<CScopeGuard> passRedirect;
|
||||
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>();
|
||||
@@ -685,6 +690,8 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
|
||||
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");
|
||||
|
||||
+19
-7
@@ -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;
|
||||
};
|
||||
@@ -202,9 +204,16 @@ namespace Render {
|
||||
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,
|
||||
@@ -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;
|
||||
|
||||
@@ -210,6 +210,73 @@ void CShader::getUniformLocations() {
|
||||
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");
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>()) {
|
||||
;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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>()) {
|
||||
;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
@@ -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; });
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ enum ePassElementType : uint8_t {
|
||||
EK_INNER_GLOW,
|
||||
EK_TRANSFORMED_WINDOW,
|
||||
EK_CUSTOM,
|
||||
EK_BACKDROP_SCOPE,
|
||||
};
|
||||
|
||||
class IPassElement {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "PassElement.hpp"
|
||||
#include <hyprutils/math/Region.hpp>
|
||||
#include <optional>
|
||||
|
||||
class CRectPassElement : public IPassElement {
|
||||
public:
|
||||
@@ -11,6 +12,8 @@ class CRectPassElement : public IPassElement {
|
||||
float roundingPower = 2.0f;
|
||||
bool blur = false, xray = false;
|
||||
float blurA = 1.F;
|
||||
std::optional<CBox> blurPatternBox;
|
||||
PHLWINDOWREF blurOwner;
|
||||
CBox clipBox;
|
||||
|
||||
// internal
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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>{}));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user