From d3be33998650cc103d9491516f9c791de8b6bc4e Mon Sep 17 00:00:00 2001 From: Oliver Stevns <1045397+MrStevns@users.noreply.github.com> Date: Sun, 1 Oct 2023 14:37:16 +0200 Subject: [PATCH] Improve CameraPainter and BackgroundWidget performance (#1789) * Canvaspainter improvement WIP * Implement tiled buffer for faster painting WIP * CanvasPainter painting improvements - Split painting for current frame and onion skinning - Introduce blitRect for updating only the dirty portion of the frame - Optimization: Only do an expensive fill when the size changes, otherwise rely on the blitRect to clear the dirty portion. * Only update the dirty potion Aside from cleaning up various extra update methods that are no longer necessary, I've made a minor change in vectorImage, in order to get it's correct dirty region. * Fix vector selection being slightly wider than the overall bounds This was a necessary change in order to calculate the correct bounds that wouldn't draw artifacts. * Fix stroke placement for vector polyline This also fixes some inconsistency in the CanvasPainter that I couldn't make sense of.. * Implement faster hashing * Do some cleanup * Fix next onion skin being drawn on current frame when it's the last * Re-add loadFile if the bitmap image hasn't been loaded yet Although i don't like that this exists in our painter, I believe we added it because for some reason the image hasn't been loaded yet, so let's leave it for now... ideally though i've really like to get rid of such weird mutable things that has little to do with painting. * Update CanvasPainter interface to better match its usage * Cleanup TiledBuffer * Make polyline work * Fix blur logic of smudge tool Additionally this will fix some artifacts when smudging * Replace use of BitmapImage buffer with TiledBuffer - Note that VectorImage hasn't been implemented yet. * Make TiledBuffer work for VectorImage * Cleanup dead code in TiledBuffer * Fix not painting bitmap layers when not on current frame * Add missing license * Remove dead code * Implement blitting in CameraPainter * Fix drawImage would not get proper update bounds * Cleanup TiledBuffer and fix warnings * Fix Eraser should use destinationOut composition * Fix anti aliasing was causing unwanted floating precision error This was causing the gaps to be drawn between the tiled buffer tiles in some cases... particularly when erasing and you had zoomed in or out. * Fix Polyline can't be removed after applying on vector * Fix transformed selection was being painted in wrong order * Cleanup after testing * Fix tabletRestorePrevTool would always ask for a full canvas update * Remove unnecessary clear of cache when setting tool * Fix update artifacts when using dotted cursor - In addition this fix makes it possible to ignore an additional update event which would previously be done by updateCanvasCursor, but because the TiledBuffer will handle this now, we only update the cursor when a tool is not active. * Refactor TiledBuffer - Also add missing onClearTile callback, which is unused but implementation wise is correct now. * Some cleanup * Remove redundant clear of tile before deleting it * Update signal/slot naming convention * Apply some refactoring to tile * Fix blitRect::extend would make tiles odd numbered * Remove unused methods * Avoid invalidating cache till we're done drawing * Remove various dead code from ScribbleArea * Fix ScribbleArea repaint when tiled buffer does not cover update rect * Fix canvas pixmap being too big when using devicePixelRatio > 1 * Apply clip rect logic to BackgroundWidget * Try to fix seams appearing in Qt 6 * Fix canvas not being updated when using camera tool * Cleanup CanvasPainter * Adjust blitRect explanation * Remove the need to allocate an empty QPointF to draw pixmaps * TiledBuffer: Remove unused signals * Use clipRect instead of paint(target, pix, source) in order to support HDPI properly --- core_lib/src/camerapainter.cpp | 98 +++++++++++++-------- core_lib/src/camerapainter.h | 24 ++--- core_lib/src/interface/backgroundwidget.cpp | 7 +- core_lib/src/interface/backgroundwidget.h | 2 +- core_lib/src/interface/scribblearea.cpp | 8 +- 5 files changed, 83 insertions(+), 56 deletions(-) diff --git a/core_lib/src/camerapainter.cpp b/core_lib/src/camerapainter.cpp index e33c0ebbcd..ca4a40993f 100644 --- a/core_lib/src/camerapainter.cpp +++ b/core_lib/src/camerapainter.cpp @@ -28,9 +28,21 @@ GNU General Public License for more details. #include "painterutils.h" -CameraPainter::CameraPainter() +CameraPainter::CameraPainter(QPixmap& canvas) : mCanvas(canvas) { + reset(); +} +void CameraPainter::reset() +{ + mCameraPixmap = QPixmap(mCanvas.size()); + mCameraPixmap.setDevicePixelRatio(mCanvas.devicePixelRatioF()); + mCameraPixmap.fill(Qt::transparent); +} + +void CameraPainter::resetCache() +{ + mCameraCacheValid = false; } void CameraPainter::preparePainter(const Object* object, @@ -52,50 +64,51 @@ void CameraPainter::preparePainter(const Object* object, mViewScale = viewScale; } -void CameraPainter::paint() const +void CameraPainter::paint(const QRect& blitRect) { QPainter painter; - initializePainter(painter, *mCanvas); - paintVisuals(painter); + initializePainter(painter, mCanvas, blitRect, false); + paintVisuals(painter, blitRect); + + mCameraCacheValid = true; } -void CameraPainter::paintCached() +void CameraPainter::paintCached(const QRect& blitRect) { - if (!mCachedPaint) { - QPainter tempPainter; - QPixmap cachedPixmap(mCanvas->size()); - cachedPixmap.fill(Qt::transparent); - initializePainter(tempPainter, cachedPixmap); - - paintVisuals(tempPainter); - mCachedPaint.reset(new QPixmap(cachedPixmap)); - tempPainter.end(); - } - QPainter painter; - initializePainter(painter, *mCanvas); - painter.drawPixmap(0, 0, *mCachedPaint.get()); - painter.end(); + // As always, initialize the painter with the canvas image, as this is what we'll paint on + // In this case though because the canvas has already been painted, we're not interested in + // having the blitter clear the image again, as that would remove our previous painted data, ie. strokes... + initializePainter(painter, mCanvas, blitRect, false); + if (!mCameraCacheValid) { + paintVisuals(painter, blitRect); + painter.end(); + mCameraCacheValid = true; + } else { + painter.setWorldMatrixEnabled(false); + painter.drawPixmap(mZeroPoint, mCameraPixmap); + painter.setWorldMatrixEnabled(true); + painter.end(); + } } -void CameraPainter::setCanvas(QPixmap* canvas) +void CameraPainter::initializePainter(QPainter& painter, QPixmap& pixmap, const QRect& blitRect, bool blitEnabled) { - mCanvas = canvas; -} + painter.begin(&pixmap); -void CameraPainter::resetCache() -{ - mCachedPaint.reset(); -} + if (blitEnabled) { + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.fillRect(blitRect, Qt::transparent); + // Surface has been cleared and is ready to be painted on + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + } -void CameraPainter::initializePainter(QPainter& painter, QPixmap& pixmap) const -{ - painter.begin(&pixmap); + painter.setClipRect(blitRect); + painter.setWorldMatrixEnabled(true); painter.setWorldTransform(mViewTransform); - painter.setWorldMatrixEnabled(false); } -void CameraPainter::paintVisuals(QPainter& painter) const +void CameraPainter::paintVisuals(QPainter& painter, const QRect& blitRect) { LayerCamera* cameraLayerBelow = static_cast(mObject->getLayerBelow(mCurrentLayerIndex, Layer::CAMERA)); @@ -105,6 +118,9 @@ void CameraPainter::paintVisuals(QPainter& painter) const if (mLayerVisibility == LayerVisibility::CURRENTONLY && currentLayer->type() != Layer::CAMERA) { return; } + QPainter visualsPainter; + initializePainter(visualsPainter, mCameraPixmap, blitRect, true); + if (!mIsPlaying || mOnionSkinOptions.enabledWhilePlaying) { int startLayerI = 0; @@ -117,15 +133,15 @@ void CameraPainter::paintVisuals(QPainter& painter) const bool isCurrentLayer = cameraLayer == cameraLayerBelow; - painter.save(); - painter.setOpacity(1); + visualsPainter.save(); + visualsPainter.setOpacity(1); if (mLayerVisibility == LayerVisibility::RELATED && !isCurrentLayer) { - painter.setOpacity(calculateRelativeOpacityForLayer(mCurrentLayerIndex, i, mRelativeLayerOpacityThreshold)); + visualsPainter.setOpacity(calculateRelativeOpacityForLayer(mCurrentLayerIndex, i, mRelativeLayerOpacityThreshold)); } - paintOnionSkinning(painter, cameraLayer); + paintOnionSkinning(visualsPainter, cameraLayer); - painter.restore(); + visualsPainter.restore(); } } @@ -133,10 +149,13 @@ void CameraPainter::paintVisuals(QPainter& painter) const QTransform camTransform = cameraLayerBelow->getViewAtFrame(mFrameIndex); QRect cameraRect = cameraLayerBelow->getViewRect(); - paintBorder(painter, camTransform, cameraRect); + paintBorder(visualsPainter, camTransform, cameraRect); + + painter.setWorldMatrixEnabled(false); + painter.drawPixmap(mZeroPoint, mCameraPixmap); } -void CameraPainter::paintBorder(QPainter& painter, const QTransform& camTransform, const QRect& camRect) const +void CameraPainter::paintBorder(QPainter& painter, const QTransform& camTransform, const QRect& camRect) { painter.save(); QRect viewRect = painter.viewport(); @@ -159,12 +178,13 @@ void CameraPainter::paintBorder(QPainter& painter, const QTransform& camTransfor painter.restore(); } -void CameraPainter::paintOnionSkinning(QPainter& painter, const LayerCamera* cameraLayer) const +void CameraPainter::paintOnionSkinning(QPainter& painter, const LayerCamera* cameraLayer) { QPen onionSkinPen; painter.save(); painter.setBrush(Qt::NoBrush); + painter.setWorldMatrixEnabled(false); onionSkinPen.setStyle(Qt::PenStyle::DashLine); mOnionSkinPainter.paint(painter, cameraLayer, mOnionSkinOptions, mFrameIndex, [&] (OnionSkinPaintState state, int onionSkinNumber) { diff --git a/core_lib/src/camerapainter.h b/core_lib/src/camerapainter.h index 1bfa1a9c24..45dcac50f4 100644 --- a/core_lib/src/camerapainter.h +++ b/core_lib/src/camerapainter.h @@ -37,28 +37,31 @@ class KeyFrame; class CameraPainter { public: - explicit CameraPainter(); + explicit CameraPainter(QPixmap& canvas); - void paint() const; - void paintCached(); + void paint(const QRect& blitRect); + void paintCached(const QRect& blitRect); void setOnionSkinPainterOptions(const OnionSkinPainterOptions& options) { mOnionSkinOptions = options; } - void setCanvas(QPixmap* canvas); void preparePainter(const Object* object, int layerIndex, int frameIndex, const QTransform& transform, bool isPlaying, LayerVisibility layerVisibility, float relativeLayerOpacityThreshold, qreal viewScale); + void reset(); + void resetCache(); private: - void initializePainter(QPainter& painter, QPixmap& pixmap) const; - void paintVisuals(QPainter& painter) const; - void paintBorder(QPainter& painter, const QTransform& camTransform, const QRect& camRect) const; - void paintOnionSkinning(QPainter& painter, const LayerCamera* cameraLayer) const; + void initializePainter(QPainter& painter, QPixmap& pixmap, const QRect& blitRect, bool blitEnabled); + void paintVisuals(QPainter& painter, const QRect& blitRect); + void paintBorder(QPainter& painter, const QTransform& camTransform, const QRect& camRect); + void paintOnionSkinning(QPainter& painter, const LayerCamera* cameraLayer); const Object* mObject = nullptr; - QPixmap* mCanvas = nullptr; + QPixmap& mCanvas; - std::unique_ptr mCachedPaint = nullptr; + QPixmap mCameraPixmap; QTransform mViewTransform; + const QPointF mZeroPoint; + OnionSkinSubPainter mOnionSkinPainter; OnionSkinPainterOptions mOnionSkinOptions; @@ -69,6 +72,7 @@ class CameraPainter qreal mViewScale = 0; bool mIsPlaying = false; + bool mCameraCacheValid = false; }; #endif // CAMERAPAINTER_H diff --git a/core_lib/src/interface/backgroundwidget.cpp b/core_lib/src/interface/backgroundwidget.cpp index ebee13a2b6..b51aad4127 100644 --- a/core_lib/src/interface/backgroundwidget.cpp +++ b/core_lib/src/interface/backgroundwidget.cpp @@ -19,6 +19,7 @@ GNU General Public License for more details. #include #include +#include BackgroundWidget::BackgroundWidget(QWidget* parent) : QWidget(parent) @@ -66,11 +67,13 @@ void BackgroundWidget::settingUpdated(SETTING setting) } } -void BackgroundWidget::paintEvent(QPaintEvent *) +void BackgroundWidget::paintEvent(QPaintEvent* event) { QStyleOption opt; opt.initFrom(this); QPainter painter(this); + painter.setClipRect(event->rect()); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); if (mHasShadow) @@ -112,7 +115,7 @@ void BackgroundWidget::loadBackgroundStyle() setStyleSheet(mStyle); } -void BackgroundWidget::drawShadow( QPainter& painter ) +void BackgroundWidget::drawShadow(QPainter& painter) { int radius1 = 12; int radius2 = 8; diff --git a/core_lib/src/interface/backgroundwidget.h b/core_lib/src/interface/backgroundwidget.h index 881a41a397..9c58fc11ff 100644 --- a/core_lib/src/interface/backgroundwidget.h +++ b/core_lib/src/interface/backgroundwidget.h @@ -35,7 +35,7 @@ public slots: protected: - void paintEvent( QPaintEvent* ) override; + void paintEvent(QPaintEvent* event) override; private slots: diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 028068eda7..7e6d58ede8 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -45,7 +45,7 @@ GNU General Public License for more details. #include "selectionmanager.h" #include "overlaymanager.h" -ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent), mCanvasPainter(mCanvas) +ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent), mCanvasPainter(mCanvas), mCameraPainter(mCanvas) { setObjectName("ScribbleArea"); @@ -825,6 +825,7 @@ void ScribbleArea::resizeEvent(QResizeEvent* event) invalidateCacheForFrame(mEditor->currentFrame()); invalidatePainterCaches(); mCanvasPainter.reset(); + mCameraPainter.reset(); } void ScribbleArea::showLayerNotVisibleWarning() @@ -1010,7 +1011,7 @@ void ScribbleArea::paintEvent(QPaintEvent* event) prepOverlays(currentFrame); mCanvasPainter.paintCached(event->rect()); - mCameraPainter.paintCached(); + mCameraPainter.paintCached(event->rect()); } if (currentTool()->type() == MOVE) @@ -1193,7 +1194,6 @@ void ScribbleArea::prepCameraPainter(int frame) onionSkinOptions.minOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY); mCameraPainter.setOnionSkinPainterOptions(onionSkinOptions); - mCameraPainter.setCanvas(&mCanvas); } void ScribbleArea::prepCanvas(int frame, QRect rect) @@ -1240,7 +1240,7 @@ void ScribbleArea::drawCanvas(int frame, QRect rect) prepCameraPainter(frame); prepOverlays(frame); mCanvasPainter.paint(rect); - mCameraPainter.paint(); + mCameraPainter.paint(rect); } void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset)