From 7be81e584e741e5ebcd5ff292fae87164411ddc3 Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 20 Jun 2026 20:55:15 +0200 Subject: [PATCH] GPU caching for better performance after many strokes --- Plugins/ZShell/Internal/stroke.hpp | 3 ++ Plugins/ZShell/Internal/strokecanvasitem.cpp | 46 +++++++++++++++-- Plugins/ZShell/Internal/strokecanvasitem.hpp | 1 + .../ZShell/Internal/strokecanvasrenderer.cpp | 49 ++++++++++++------- .../ZShell/Internal/strokecanvasrenderer.hpp | 2 + 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Plugins/ZShell/Internal/stroke.hpp b/Plugins/ZShell/Internal/stroke.hpp index baad605..132e3cf 100644 --- a/Plugins/ZShell/Internal/stroke.hpp +++ b/Plugins/ZShell/Internal/stroke.hpp @@ -3,13 +3,16 @@ #include #include #include +#include namespace ZShell::internal { struct Stroke { QVector points; + QCanvasPath path; QColor color; float width; + int groupId = -1; }; }; diff --git a/Plugins/ZShell/Internal/strokecanvasitem.cpp b/Plugins/ZShell/Internal/strokecanvasitem.cpp index e8b4ec6..6036bd9 100644 --- a/Plugins/ZShell/Internal/strokecanvasitem.cpp +++ b/Plugins/ZShell/Internal/strokecanvasitem.cpp @@ -1,5 +1,6 @@ #include "strokecanvasitem.hpp" #include "strokecanvasrenderer.hpp" +#include #include namespace ZShell::internal { @@ -27,6 +28,42 @@ static bool shouldAddPoint( >= minDistance * minDistance; } +static QCanvasPath buildStrokePath(const QVector &points) { + QCanvasPath path; + + if (points.size() == 1) { + // Single point — store as a tiny circle so the group can still be cached + path.circle(points[0], 0); // radius 0; actual width applied at draw time + return path; + } + + auto catmullToBezier = []( + const QPointF &p0, const QPointF &p1, + const QPointF &p2, const QPointF &p3, + float tension, + QPointF &cp1, QPointF &cp2) + { + cp1 = p1 + (p2 - p0) * tension / 3.0f; + cp2 = p2 - (p3 - p1) * tension / 3.0f; + }; + + const float tension = 0.5f; + path.moveTo(points[0]); + + for (int i = 0; i < points.size() - 1; ++i) { + const QPointF &p0 = points[qMax(i - 1, 0)]; + const QPointF &p1 = points[i]; + const QPointF &p2 = points[i + 1]; + const QPointF &p3 = points[qMin(i + 2, points.size() - 1)]; + + QPointF cp1, cp2; + catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2); + path.bezierCurveTo(cp1, cp2, p2); + } + + return path; +} + void StrokeCanvasItem::setPenColor(const QColor &color) { if (m_penColor == color) return; @@ -129,16 +166,19 @@ void StrokeCanvasItem::endStroke() { if (m_currentStroke.points.isEmpty()) return; - m_strokes.append(m_currentStroke); + m_currentStroke.path = buildStrokePath(m_currentStroke.points); + m_currentStroke.groupId = m_nextGroupId++; m_currentStroke.points.clear(); + m_strokes.append(m_currentStroke); + m_currentStroke = {}; + update(); } void StrokeCanvasItem::clear() { m_strokes.clear(); - m_currentStroke.points.clear(); - + m_currentStroke = {}; update(); } diff --git a/Plugins/ZShell/Internal/strokecanvasitem.hpp b/Plugins/ZShell/Internal/strokecanvasitem.hpp index 0a39a08..ed70f62 100644 --- a/Plugins/ZShell/Internal/strokecanvasitem.hpp +++ b/Plugins/ZShell/Internal/strokecanvasitem.hpp @@ -71,6 +71,7 @@ QPointF m_hoverPoint; QColor m_penColor = Qt::white; float m_penWidth = 4.f; +int m_nextGroupId = 0; QVector m_strokes; Stroke m_currentStroke; }; diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp index 2f8924a..4edfa7d 100644 --- a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp @@ -73,7 +73,18 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { m_penColor = canvas->m_penColor; m_penWidth = canvas->m_penWidth; - m_strokes = canvas->m_strokes; + // Only copy strokes the renderer hasn't seen yet + while (m_strokes.size() < canvas->m_strokes.size()) + m_strokes.append(canvas->m_strokes[m_strokes.size()]); + + // Handle clear() + if (canvas->m_strokes.isEmpty() && !m_strokes.isEmpty()) { + for (const auto &stroke : m_strokes) + if (stroke.groupId >= 0) + m_pendingGroupRemovals.append(stroke.groupId); + m_strokes.clear(); + } + m_currentStroke = canvas->m_currentStroke; m_hoverVisible = canvas->m_hoverVisible; @@ -81,26 +92,30 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { } void StrokeCanvasRenderer::paint(QCanvasPainter *painter) { + for (int id : m_pendingGroupRemovals) + painter->removePathGroup(id); + m_pendingGroupRemovals.clear(); + painter->clearRect(0, 0, width(), height()); - for (const auto &stroke : m_strokes) - drawStroke(painter, stroke.points, stroke.color, stroke.width); + for (const auto &stroke : m_strokes) { + painter->setStrokeStyle(stroke.color); + painter->setFillStyle(stroke.color); + painter->setLineWidth(stroke.width); + painter->setLineCap(QCanvasPainter::LineCap::Round); + painter->setLineJoin(QCanvasPainter::LineJoin::Round); - drawStroke( - painter, - m_currentStroke.points, - m_currentStroke.color, - m_currentStroke.width - ); - - if (m_hoverVisible) { - drawDot( - painter, - m_hoverPoint, - m_penColor, - m_penWidth - ); + if (stroke.path.commandsSize() == 1) { + painter->fill(stroke.path, stroke.groupId); + } else { + painter->stroke(stroke.path, stroke.groupId); + } } + + drawStroke(painter, m_currentStroke.points, m_currentStroke.color, m_currentStroke.width); + + if (m_hoverVisible) + drawDot(painter, m_hoverPoint, m_penColor, m_penWidth); } }; diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.hpp b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp index d9843b2..83c74b9 100644 --- a/Plugins/ZShell/Internal/strokecanvasrenderer.hpp +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "stroke.hpp" namespace ZShell::internal { @@ -19,6 +20,7 @@ QPointF m_hoverPoint; QVector m_strokes; Stroke m_currentStroke; +QVector m_pendingGroupRemovals; };