improve performace of SDF

- Avoid fullscreen shading every pixel
       - Evaluate each BlobRect: localized quad, only nearby rects, instead of all 8 rects
       - O(1-3) per pixel for most passes, rather than O(N²) with N=8(~72 iterations).
       - Closed panels excluded entirely from loop.
This commit is contained in:
Robin Seger 2026-03-25 19:45:45 +01:00
parent f6d7e40f35
commit 6103ce19e8
5 changed files with 139 additions and 21 deletions

View file

@ -153,7 +153,6 @@ Variants {
PanelBg {
id: dashBg
group: blobGroup
panel: panels.dashboard
bar: bar
deformAmount: 0.1
@ -162,7 +161,6 @@ Variants {
PanelBg {
id: launcherBg
group: blobGroup
panel: panels.launcher
bar: bar
deformAmount: 0.1
@ -171,7 +169,6 @@ Variants {
PanelBg {
id: sessionBg
group: blobGroup
panel: panels.session
bar: bar
deformAmount: 0.25
@ -180,7 +177,6 @@ Variants {
PanelBg {
id: sidebarBg
group: blobGroup
panel: panels.sidebar
bar: bar
deformAmount: 0
@ -189,7 +185,6 @@ Variants {
PanelBg {
id: osdBg
group: blobGroup
panel: panels.osd
bar: bar
deformAmount: 0.3
@ -198,7 +193,6 @@ Variants {
PanelBg {
id: notifsBg
group: blobGroup
panel: panels.notifications
bar: bar
}
@ -206,7 +200,6 @@ Variants {
PanelBg {
id: utilsBg
group: blobGroup
panel: panels.utilities
bar: bar
}
@ -214,7 +207,6 @@ Variants {
PanelBg {
id: popoutBg
group: blobGroup
panel: panels.popouts
bar: bar
@ -306,6 +298,7 @@ Variants {
required property Item bar
property real deformAmount: 0.15
group: panel.width > 0 && panel.height > 0 ? blobGroup : null
x: panel.x + bar.implicitWidth
y: panel.y + Config.border.thickness
implicitWidth: panel.width

View file

@ -1,9 +1,126 @@
#include "blobinvertedrect.hpp"
#include "blobgroup.hpp"
#include "blobmaterial.hpp"
#include <qsggeometry.h>
#include <qsgnode.h>
#include <algorithm>
#include <cstring>
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent)
: BlobShape(parent) {}
static void setFrameIndices(quint16* idx) {
// Top strip: 0-1-4, 1-5-4
idx[0] = 0; idx[1] = 1; idx[2] = 4;
idx[3] = 1; idx[4] = 5; idx[5] = 4;
// Right strip: 1-2-5, 2-6-5
idx[6] = 1; idx[7] = 2; idx[8] = 5;
idx[9] = 2; idx[10] = 6; idx[11] = 5;
// Bottom strip: 2-3-6, 3-7-6
idx[12] = 2; idx[13] = 3; idx[14] = 6;
idx[15] = 3; idx[16] = 7; idx[17] = 6;
// Left strip: 3-0-7, 0-4-7
idx[18] = 3; idx[19] = 0; idx[20] = 7;
idx[21] = 0; idx[22] = 4; idx[23] = 7;
}
QSGNode* BlobInvertedRect::updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData*) {
if (!m_group) {
delete oldNode;
return nullptr;
}
const float pad = static_cast<float>(m_group->smoothing());
// Compute inner hole boundary in local coords
// Inset past the inner border edge by 2x smoothing to cover the blend zone
const float inset = pad * 2.0f;
const float holeLeft = static_cast<float>(m_borderLeft) + inset;
const float holeTop = static_cast<float>(m_borderTop) + inset;
const float holeRight = static_cast<float>(width() - m_borderRight) - inset;
const float holeBot = static_cast<float>(height() - m_borderBottom) - inset;
// If the hole is too small or invalid, fall back to full quad
if (holeLeft >= holeRight || holeTop >= holeBot)
return BlobShape::updatePaintNode(oldNode, nullptr);
auto* node = static_cast<QSGGeometryNode*>(oldNode);
const bool needsRebuild = !node || node->geometry()->vertexCount() != 8;
if (needsRebuild) {
delete oldNode;
node = new QSGGeometryNode;
auto* geometry = new QSGGeometry(
QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24,
QSGGeometry::UnsignedShortType);
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
setFrameIndices(geometry->indexDataAsUShort());
auto* material = new BlobMaterial;
material->setFlag(QSGMaterial::Blending);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
}
// Outer bounds (local coords)
const float x0 = static_cast<float>(m_localPaddedRect.x());
const float y0 = static_cast<float>(m_localPaddedRect.y());
const float x1 = x0 + static_cast<float>(m_localPaddedRect.width());
const float y1 = y0 + static_cast<float>(m_localPaddedRect.height());
const float w = x1 - x0;
const float h = y1 - y0;
// Update vertex positions and texture coordinates
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
// Outer corners
v[0].set(x0, y0, 0.0f, 0.0f);
v[1].set(x1, y0, 1.0f, 0.0f);
v[2].set(x1, y1, 1.0f, 1.0f);
v[3].set(x0, y1, 0.0f, 1.0f);
// Inner corners (hole)
v[4].set(holeLeft, holeTop, (holeLeft - x0) / w, (holeTop - y0) / h);
v[5].set(holeRight, holeTop, (holeRight - x0) / w, (holeTop - y0) / h);
v[6].set(holeRight, holeBot, (holeRight - x0) / w, (holeBot - y0) / h);
v[7].set(holeLeft, holeBot, (holeLeft - x0) / w, (holeBot - y0) / h);
node->markDirty(QSGNode::DirtyGeometry);
// Update material uniforms
auto* material = static_cast<BlobMaterial*>(node->material());
material->m_paddedX = m_cachedPaddedX;
material->m_paddedY = m_cachedPaddedY;
material->m_paddedW = m_cachedPaddedW;
material->m_paddedH = m_cachedPaddedH;
material->m_smoothFactor = pad;
material->m_myIndex = m_cachedMyIndex;
material->m_color = m_group->color();
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
material->m_invertedRadius = m_cachedInvertedRadius;
memcpy(material->m_invertedOuter, m_cachedInvertedOuter,
sizeof(m_cachedInvertedOuter));
memcpy(material->m_invertedInner, m_cachedInvertedInner,
sizeof(m_cachedInvertedInner));
const int count =
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
material->m_rectCount = count;
for (int i = 0; i < count; ++i)
material->m_rects[i] = m_cachedRects[i];
node->markDirty(QSGNode::DirtyMaterial);
return node;
}
BlobInvertedRect::~BlobInvertedRect() {
if (m_group)
m_group->clearInvertedRect(this);

View file

@ -45,6 +45,8 @@ signals:
protected:
bool isInvertedRect() const override { return true; }
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
void registerWithGroup() override;
void unregisterFromGroup() override;

View file

@ -102,10 +102,6 @@ void BlobShape::updatePolish() {
// Ensure all shapes have up-to-date physics (only once per frame)
m_group->ensurePhysicsUpdated();
// When inverted rect renders everything, skip spatial query for others
if (!isInvertedRect() && m_group->invertedRect())
return;
const QPointF scenePos = mapToScene(QPointF(0, 0));
const float pad = static_cast<float>(m_group->smoothing());
@ -142,6 +138,10 @@ void BlobShape::updatePolish() {
if (other->isInvertedRect())
continue;
// Skip zero-size rects
if (other->width() <= 0 || other->height() <= 0)
continue;
const QPointF otherScene = other->mapToScene(QPointF(0, 0));
bool include = false;
@ -299,12 +299,6 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
return nullptr;
}
// When an inverted rect exists, it renders everything in a single pass
if (!isInvertedRect() && m_group->invertedRect()) {
delete oldNode;
return nullptr;
}
auto* node = static_cast<QSGGeometryNode*>(oldNode);
if (!node) {
node = new QSGGeometryNode;

View file

@ -85,6 +85,11 @@ void main() {
// Offset center for asymmetric deformation
vec2 center = rect.xy + props.yz;
// AABB early-out: skip rects far from this pixel
vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
if (abs(pixel.x - center.x) > extent.x || abs(pixel.y - center.y) > extent.y)
continue;
// Apply pre-computed inverse deformation to the evaluation point
mat2 invDeform = mat2(invDm.xy, invDm.zw);
vec2 transformedPixel = center + invDeform * (pixel - center);
@ -157,6 +162,12 @@ void main() {
vec2 jSh = rectData[j * 5 + 3].xy;
vec2 jC = jR.xy + jP.yz;
// Skip non-adjacent rects
float sinkRange = smoothFactor * 1.5;
if (abs(center.x - jC.x) > iSh.x + jSh.x + sinkRange ||
abs(center.y - jC.y) > iSh.y + jSh.y + sinkRange)
continue;
// Penetration of j past i's edges (positive = past)
float pT = (jC.y + jSh.y) - (center.y - iSh.y) - sinkOff;
float pB = (center.y + iSh.y) - (jC.y - jSh.y) - sinkOff;
@ -266,9 +277,10 @@ void main() {
}
}
// myIndex == -1: inverted rect renders everything (frame + blobs)
// myIndex >= 0: individual rect renders only its owned pixels
if (myIndex >= 0 && owner != myIndex)
// Each renderer only outputs pixels it owns
// myIndex == -1: inverted rect renders border-owned pixels
// myIndex >= 0: individual rect renders its owned pixels
if (owner != myIndex)
discard;
float fw = fwidth(mergedSdf);