fix: blobs now allow to be drawn in device pixels to stay sharp
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 25s
Python / static (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Failing after 1m25s
Rust / fmt (pull_request) Successful in 39s
Rust / clippy (pull_request) Successful in 59s
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 25s
Python / static (pull_request) Successful in 34s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Failing after 1m25s
Rust / fmt (pull_request) Successful in 39s
Rust / clippy (pull_request) Successful in 59s
This commit is contained in:
@@ -29,8 +29,15 @@ static float cornerFillFactor(float sd, float smoothFactor) {
|
||||
return std::max(outside, inside);
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int k_pumpMs = 16;
|
||||
} // namespace
|
||||
|
||||
BlobGroup2::BlobGroup2(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setFlag(ItemHasContents);
|
||||
m_physicsPump.setInterval(k_pumpMs);
|
||||
m_physicsPump.setTimerType(Qt::CoarseTimer);
|
||||
connect(&m_physicsPump, &QTimer::timeout, this, &BlobGroup2::pumpPhysics);
|
||||
}
|
||||
|
||||
BlobGroup2::~BlobGroup2() {
|
||||
@@ -41,6 +48,7 @@ BlobGroup2::~BlobGroup2() {
|
||||
}
|
||||
|
||||
void BlobGroup2::setSmoothing(qreal s) {
|
||||
if (s < 0.5) s = 0.5;
|
||||
if (qFuzzyCompare(m_smoothing, s)) return;
|
||||
m_smoothing = s;
|
||||
emit smoothingChanged();
|
||||
@@ -101,17 +109,11 @@ void BlobGroup2::clearInvertedRect(BlobInvertedRect2* rect) {
|
||||
void BlobGroup2::markDirty() {
|
||||
m_physicsUpdated = false;
|
||||
m_dataBuilt = false;
|
||||
// Physics runs during polish, before the render pass. The panel content is
|
||||
// transformed by deformMatrix from QML, so the matrix has to be final for this
|
||||
// frame; building the data in updatePaintNode would let the transform lag one
|
||||
// frame behind the material.
|
||||
polish();
|
||||
update();
|
||||
}
|
||||
|
||||
void BlobGroup2::updatePolish() {
|
||||
// The group is the only thing that draws, so it also drives the shapes'
|
||||
// physics.
|
||||
ensureDataBuilt();
|
||||
}
|
||||
|
||||
@@ -122,32 +124,53 @@ void BlobGroup2::ensurePhysicsUpdated() {
|
||||
shape->updatePhysics();
|
||||
}
|
||||
|
||||
void BlobGroup2::requestPhysics() {
|
||||
if (!m_physicsPump.isActive()) m_physicsPump.start();
|
||||
}
|
||||
|
||||
void BlobGroup2::pumpPhysics() {
|
||||
for (auto* shape : std::as_const(m_shapes))
|
||||
shape->updatePhysics();
|
||||
|
||||
m_physicsUpdated = true;
|
||||
|
||||
const bool anyMoving = std::any_of(
|
||||
m_shapes.cbegin(), m_shapes.cend(), [](const BlobShape2* shape) {
|
||||
return shape->needsFrame();
|
||||
});
|
||||
|
||||
if (!anyMoving) {
|
||||
m_physicsPump.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
m_dataBuilt = false;
|
||||
polish();
|
||||
update();
|
||||
}
|
||||
|
||||
void BlobGroup2::ensureDataBuilt() {
|
||||
if (m_dataBuilt) return;
|
||||
ensurePhysicsUpdated();
|
||||
|
||||
// Physics can request the next frame through markDirty(); the data is
|
||||
// refreshed here anyway, so the flag is set after it runs.
|
||||
m_dataBuilt = true;
|
||||
m_data.rects.clear();
|
||||
m_bounds = QRectF();
|
||||
m_data.hasInverted = false;
|
||||
m_data.invertedRadius = 0;
|
||||
memset(m_data.invertedOuter, 0, sizeof(m_data.invertedOuter));
|
||||
memset(m_data.invertedInner, 0, sizeof(m_data.invertedInner));
|
||||
memset(m_data.invertedOuterRadii, 0, sizeof(m_data.invertedOuterRadii));
|
||||
|
||||
const float smoothFactor = static_cast<float>(m_smoothing);
|
||||
// The same reach the shader uses to decide whether a rect can contribute, plus
|
||||
// the shadow, so every pixel the shader can give a value to is inside the node.
|
||||
const double extent = static_cast<double>(smoothFactor) * 1.5 +
|
||||
static_cast<double>(m_shadowRadius);
|
||||
|
||||
// Group order, no pruning by proximity: the shader sees this list for every pixel. A
|
||||
// shape that is not pushing is not in the list at all: shapes here are data for the
|
||||
// group draw, so leaving a panel at rest out means it cannot define any part of the
|
||||
// edge. A closing panel is still in the list while its curve runs out, even with no
|
||||
// size left, which is what keeps the bounce.
|
||||
// entryShapes maps a shared entry back to the shape it came from.
|
||||
const bool dropAtRest = m_invertedRect != nullptr;
|
||||
QVector<BlobShape2*> entryShapes;
|
||||
|
||||
for (auto* shape : std::as_const(m_shapes)) {
|
||||
if (!shape->isMoving()) continue;
|
||||
if (dropAtRest && !shape->isMoving()) continue;
|
||||
if (shape->width() <= 0 || shape->height() <= 0) continue;
|
||||
if (static_cast<int>(m_data.rects.size()) >= BlobGroupData::kMaxRects)
|
||||
continue;
|
||||
@@ -194,7 +217,6 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
|
||||
const int count = static_cast<int>(m_data.rects.size());
|
||||
|
||||
// Exclusion gates only the smooth blend.
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int mask = 0;
|
||||
BlobShape2* const si = entryShapes[i];
|
||||
@@ -214,10 +236,11 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
BlobShape2* const si = entryShapes[i];
|
||||
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
||||
|
||||
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
|
||||
const float cBrX = ri.cx + ri.hw, cBrY = ri.cy + ri.hh;
|
||||
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
|
||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
||||
const float dcx = ri.cx + ri.offsetX, dcy = ri.cy + ri.offsetY;
|
||||
const float cTrX = dcx + ri.hw, cTrY = dcy - ri.hh;
|
||||
const float cBrX = dcx + ri.hw, cBrY = dcy + ri.hh;
|
||||
const float cBlX = dcx - ri.hw, cBlY = dcy + ri.hh;
|
||||
const float cTlX = dcx - ri.hw, cTlY = dcy - ri.hh;
|
||||
|
||||
if (cornerFill) {
|
||||
for (int j = 0; j < count; ++j) {
|
||||
@@ -227,37 +250,41 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
|
||||
continue;
|
||||
const auto& rj = m_data.rects[j];
|
||||
const float jcx = rj.cx + rj.offsetX;
|
||||
const float jcy = rj.cy + rj.offsetY;
|
||||
fTr = std::min(
|
||||
fTr,
|
||||
cornerFillFactor(
|
||||
cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh),
|
||||
cpuSdBox(cTrX, cTrY, jcx, jcy, rj.hw, rj.hh),
|
||||
smoothFactor));
|
||||
fBr = std::min(
|
||||
fBr,
|
||||
cornerFillFactor(
|
||||
cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh),
|
||||
cpuSdBox(cBrX, cBrY, jcx, jcy, rj.hw, rj.hh),
|
||||
smoothFactor));
|
||||
fBl = std::min(
|
||||
fBl,
|
||||
cornerFillFactor(
|
||||
cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh),
|
||||
cpuSdBox(cBlX, cBlY, jcx, jcy, rj.hw, rj.hh),
|
||||
smoothFactor));
|
||||
fTl = std::min(
|
||||
fTl,
|
||||
cornerFillFactor(
|
||||
cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh),
|
||||
cpuSdBox(cTlX, cTlY, jcx, jcy, rj.hw, rj.hh),
|
||||
smoothFactor));
|
||||
}
|
||||
}
|
||||
|
||||
ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
|
||||
ri.radius[1] = std::max(ri.radius[1] * fBr, minR);
|
||||
ri.radius[2] = std::max(ri.radius[2] * fBl, minR);
|
||||
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
||||
ri.radius[0] = ri.radius[0] > 0.0f ? std::max(ri.radius[0] * fTr, minR)
|
||||
: 0.0f;
|
||||
ri.radius[1] = ri.radius[1] > 0.0f ? std::max(ri.radius[1] * fBr, minR)
|
||||
: 0.0f;
|
||||
ri.radius[2] = ri.radius[2] > 0.0f ? std::max(ri.radius[2] * fBl, minR)
|
||||
: 0.0f;
|
||||
ri.radius[3] = ri.radius[3] > 0.0f ? std::max(ri.radius[3] * fTl, minR)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
// Inverted rect. The frame term is evaluated for every pixel in the bounds,
|
||||
// so the group flag cannot be gated per shape.
|
||||
if (!m_invertedRect) return;
|
||||
|
||||
auto* inv = static_cast<BlobShape2*>(m_invertedRect);
|
||||
@@ -297,6 +324,8 @@ void BlobGroup2::ensureDataBuilt() {
|
||||
m_data.invertedInner[2] = innerHW;
|
||||
m_data.invertedInner[3] = innerHH;
|
||||
|
||||
inv->outerCornerRadii(m_data.invertedOuterRadii);
|
||||
|
||||
m_bounds = m_bounds.united(QRectF(
|
||||
static_cast<double>(outerCX) - static_cast<double>(outerHW) - extent,
|
||||
static_cast<double>(outerCY) - static_cast<double>(outerHH) - extent,
|
||||
@@ -311,7 +340,6 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
||||
material->m_paddedH = static_cast<float>(m_bounds.height());
|
||||
|
||||
material->m_smoothFactor = static_cast<float>(m_smoothing);
|
||||
// The single group draw has no owner, so the shader skips the ownership test.
|
||||
material->m_myIndex = -2;
|
||||
material->m_color = m_color;
|
||||
material->m_hasInverted = m_data.hasInverted ? 1 : 0;
|
||||
@@ -324,6 +352,10 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
||||
material->m_invertedInner,
|
||||
data().invertedInner,
|
||||
sizeof(data().invertedInner));
|
||||
memcpy(
|
||||
material->m_invertedOuterRadii,
|
||||
data().invertedOuterRadii,
|
||||
sizeof(data().invertedOuterRadii));
|
||||
|
||||
material->m_shadowRadius = static_cast<float>(m_shadowRadius);
|
||||
material->m_shadowColor[0] = static_cast<float>(m_shadowColor.redF());
|
||||
@@ -339,8 +371,6 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
||||
}
|
||||
|
||||
QSGNode* BlobGroup2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||
// Data is built during polish. This only covers a render pass that happens
|
||||
// without one.
|
||||
ensureDataBuilt();
|
||||
|
||||
if (m_data.rects.isEmpty() && !m_data.hasInverted) {
|
||||
@@ -364,10 +394,6 @@ QSGNode* BlobGroup2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||
node->setFlag(QSGNode::OwnsMaterial);
|
||||
}
|
||||
|
||||
// The shader evaluates the field at the scene position a vertex maps to, so
|
||||
// the group item itself must not be transformed. The vertex positions are the
|
||||
// bounds in this item's frame; the material carries the same rect in scene
|
||||
// coordinates.
|
||||
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
|
||||
const QPointF p0 = mapFromScene(m_bounds.topLeft());
|
||||
const QPointF p1 = mapFromScene(m_bounds.bottomRight());
|
||||
|
||||
Reference in New Issue
Block a user