Commit 029deeef authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implement proper compression for the filter dialog

The patch consists of three different changes:

1) CancelSilentlyMarker is changed from a job to an atomic flag.
   Before this change, the job used to be cancelled before the
   actual flag was reset. It caused too many unnecessary updates
   to happen.

2) KisFilterStrokeStrategy now provides a cookie to show whether
   the procesing has finished or not.

3) This cookie is used in a new mode in KisSignalCompressor. This
   mode works as the normal FIRST_ACTIVE mode, with one exception:
   it has "the second" shorter timer that checks if the queue is
   already idle. If it is idle, then the signal will be passed
   before the normal deadline. This mode is activated by calling
   the second override of setDelay() that accepts isIdle callback
   for the state of the queue.
parent fe691e04
......@@ -58,8 +58,7 @@ KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, SlowHandlerMode s
connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
}
void KisSignalCompressor::setDelay(int delay)
void KisSignalCompressor::setDelayImpl(int delay)
{
const bool wasActive = m_timer->isActive();
......@@ -74,6 +73,20 @@ void KisSignalCompressor::setDelay(int delay)
}
}
void KisSignalCompressor::setDelay(int delay)
{
m_timeout = delay;
m_idleCallback = {};
setDelayImpl(delay);
}
void KisSignalCompressor::setDelay(std::function<bool ()> idleCallback, int idleDelay, int timeout)
{
m_timeout = timeout;
m_idleCallback = idleCallback;
setDelayImpl(idleDelay);
}
void KisSignalCompressor::start()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_mode != UNDEFINED);
......@@ -138,13 +151,16 @@ bool KisSignalCompressor::tryEmitOnTick(bool isFromTimer)
// we have different requirements for hi-frequency events (the mean
// of the events rate must be min(compressorRate, eventsRate)
const int realInterval = m_timer->interval();
const int realInterval = m_timeout;
const int minInterval = realInterval < 100 ? 0.5 * realInterval : realInterval;
// Enable for debugging:
// ENTER_FUNCTION() << ppVar(isFromTimer) << ppVar(m_signalsPending) << m_lastEmittedTimer.elapsed();
// ENTER_FUNCTION() << ppVar(isFromTimer) << ppVar(m_signalsPending) << m_lastEmittedTimer.elapsed() << ppVar((m_idleCallback && m_idleCallback()));
if (m_signalsPending &&
(m_lastEmittedTimer.elapsed() >= minInterval ||
(m_idleCallback && m_idleCallback()))) {
if (m_signalsPending && m_lastEmittedTimer.elapsed() >= minInterval) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!isFromTimer || !m_isEmitting);
if (m_slowHandlerMode == PRECISE_INTERVAL) {
......@@ -188,7 +204,7 @@ void KisSignalCompressor::slotTimerExpired()
{
KIS_ASSERT_RECOVER_NOOP(m_mode != UNDEFINED);
if (!tryEmitOnTick(true)) {
const int calmDownInterval = 5 * m_timer->interval();
const int calmDownInterval = 5 * m_timeout;
if (!m_lastEmittedTimer.isValid() ||
m_lastEmittedTimer.elapsed() > calmDownInterval) {
......@@ -217,5 +233,5 @@ void KisSignalCompressor::setMode(KisSignalCompressor::Mode mode)
int KisSignalCompressor::delay() const
{
return m_timer->interval();
return m_timeout;
}
......@@ -11,6 +11,7 @@
#include "kritaglobal_export.h"
#include <QElapsedTimer>
#include <functional>
class QTimer;
......@@ -71,6 +72,8 @@ public:
int delay() const;
void setIdleCallback();
void setDelay(std::function<bool()> idleCallback, int idleDelay, int timeout);
public Q_SLOTS:
void setDelay(int delay);
......@@ -86,6 +89,7 @@ Q_SIGNALS:
private:
bool tryEmitOnTick(bool isFromTimer);
bool tryEmitSignalSafely();
void setDelayImpl(int delay);
private:
QTimer *m_timer = 0;
......@@ -94,6 +98,8 @@ private:
bool m_signalsPending = false;
QElapsedTimer m_lastEmittedTimer;
int m_isEmitting = 0;
int m_timeout = 0;
std::function<bool()> m_idleCallback;
};
#endif /* __KIS_SIGNAL_COMPRESSOR_H */
......@@ -115,6 +115,59 @@ void KisSignalCompressorTest::testSlowHandlerAdditive()
}
}
void testIdleChecksImpl(int compressorInterval,
int timerInterval,
int idleCheckInterval,
int idleDelay)
{
const int handlerDelay = 0;
QElapsedTimer elapsedTimer;
elapsedTimer.start();
CompressorTester tester(handlerDelay);
KisSignalCompressor compressor(compressorInterval,
KisSignalCompressor::FIRST_ACTIVE,
KisSignalCompressor::PRECISE_INTERVAL);
compressor.setDelay(
[idleDelay, &elapsedTimer]() {
return elapsedTimer.elapsed() > idleDelay;
},
idleCheckInterval,
compressorInterval);
QTimer timer;
timer.setInterval(timerInterval);
timer.setTimerType(Qt::PreciseTimer);
timer.setSingleShot(false);
QObject::connect(&timer, SIGNAL(timeout()), &compressor, SLOT(start()));
QObject::connect(&compressor, SIGNAL(timeout()), &tester, SLOT(start()));
QObject::connect(&compressor, &KisSignalCompressor::timeout,
[&elapsedTimer] () { elapsedTimer.restart(); });
timer.start();
QTest::qWait(500);
timer.stop();
QTest::qWait(compressorInterval * 2);
compressor.stop();
tester.dump(QString("timer %1 compressor %2 idle check %3 idle delay %4")
.arg(timerInterval).arg(compressorInterval)
.arg(idleCheckInterval).arg(idleDelay));
QTest::qWait(compressorInterval * 10);
}
void KisSignalCompressorTest::testIdleChecks()
{
for (int i = 0; i < 40; i += 3) {
testIdleChecksImpl(50, 5, 5, qMax(1, i));
}
}
QTEST_MAIN(KisSignalCompressorTest)
......
......@@ -18,6 +18,7 @@ private Q_SLOTS:
void test();
void testSlowHandlerPrecise();
void testSlowHandlerAdditive();
void testIdleChecks();
};
#endif // KISSIGNALCOMPRESSORTEST_H
......@@ -25,7 +25,7 @@
#include "kis_filter_manager.h"
#include "ui_wdgfilterdialog.h"
#include "kis_canvas2.h"
#include "kis_signal_compressor.h"
struct KisDlgFilter::Private {
Private(KisFilterManager *_filterManager, KisViewManager *_view)
......@@ -34,7 +34,13 @@ struct KisDlgFilter::Private {
, view(_view)
, filterManager(_filterManager)
, blockModifyingActionsGuard(new KisInputActionGroupsMaskGuard(view->canvasBase(), ViewTransformActionGroup))
, updateCompressor(200, KisSignalCompressor::FIRST_ACTIVE)
{
updateCompressor.setDelay(
[this] () {
return filterManager->isIdle();
},
20, 200);
}
KisFilterSP currentFilter;
......@@ -47,6 +53,7 @@ struct KisDlgFilter::Private {
// a special guard object that blocks all the painting input actions while the
// dialog is open
QScopedPointer<KisInputActionGroupsMaskGuard> blockModifyingActionsGuard;
KisSignalCompressor updateCompressor;
};
KisDlgFilter::KisDlgFilter(KisViewManager *view, KisNodeSP node, KisFilterManager *filterManager, QWidget *parent) :
......@@ -92,6 +99,7 @@ KisDlgFilter::KisDlgFilter(KisViewManager *view, KisNodeSP node, KisFilterManage
d->uiFilterDialog.checkBoxPreview->setChecked(group.readEntry("showPreview", true));
restoreGeometry(KisConfig(true).readEntry("filterdialog/geometry", QByteArray()));
connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(updatePreview()));
}
......@@ -108,7 +116,7 @@ void KisDlgFilter::setFilter(KisFilterSP f, KisFilterConfigurationSP overrideDef
d->uiFilterDialog.filterSelection->setFilter(f, overrideDefaultConfig);
d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(f->supportsAdjustmentLayers());
d->currentFilter = f;
updatePreview();
d->updateCompressor.start();
}
void KisDlgFilter::setDialogTitle(KisFilterSP filter)
......@@ -199,7 +207,7 @@ void KisDlgFilter::createMask()
void KisDlgFilter::enablePreviewToggled(bool state)
{
if (state) {
updatePreview();
d->updateCompressor.start();
} else if (d->filterManager->isStrokeRunning()) {
d->filterManager->cancel();
}
......@@ -216,7 +224,7 @@ void KisDlgFilter::filterSelectionChanged()
setDialogTitle(filter);
d->currentFilter = filter;
d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(filter.isNull() ? false : filter->supportsAdjustmentLayers());
updatePreview();
d->updateCompressor.start();
}
......
......@@ -46,10 +46,11 @@ public Q_SLOTS:
private:
void startApplyingFilter(KisFilterConfigurationSP config);
void setDialogTitle(KisFilterSP f);
void updatePreview();
private Q_SLOTS:
void slotFilterWidgetSizeChanged();
void updatePreview();
private:
struct Private;
......
......@@ -59,6 +59,8 @@ struct KisFilterManager::Private {
KisFilterConfigurationSP lastConfiguration;
KisFilterConfigurationSP currentlyAppliedConfiguration;
KisStrokeId currentStrokeId;
QSharedPointer<QAtomicInt> cancelSilentlyHandle;
KisFilterStrokeStrategy::IdleBarrierData::IdleBarrierCookie idleBarrierCookie;
QRect initialApplyRect;
QRect currentProcessRect;
......@@ -270,9 +272,12 @@ void KisFilterManager::apply(KisFilterConfigurationSP _filterConfig)
KisImageWSP image = d->view->image();
if (d->currentStrokeId) {
image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::CancelSilentlyMarker);
d->cancelSilentlyHandle->ref();
image->cancelStroke(d->currentStrokeId);
d->currentStrokeId.clear();
d->cancelSilentlyHandle.clear();
d->idleBarrierCookie.clear();
} else {
image->waitForDone();
d->initialApplyRect = d->view->activeNode()->exactBounds();
......@@ -295,13 +300,14 @@ void KisFilterManager::apply(KisFilterConfigurationSP _filterConfig)
d->view->activeNode(),
resourceManager);
KisStrokeStrategy *strategy = new KisFilterStrokeStrategy(filter,
KisFilterStrokeStrategy *strategy = new KisFilterStrokeStrategy(filter,
KisFilterConfigurationSP(filterConfig),
resources);
{
KConfigGroup group( KSharedConfig::openConfig(), "filterdialog");
strategy->setForceLodModeIfPossible(group.readEntry("forceLodMode", true));
}
d->cancelSilentlyHandle = strategy->cancelSilentlyHandle();
d->currentStrokeId =
image->startStroke(strategy);
......@@ -322,6 +328,13 @@ void KisFilterManager::apply(KisFilterConfigurationSP _filterConfig)
new KisFilterStrokeStrategy::Data(processRect, false));
}
{
KisFilterStrokeStrategy::IdleBarrierData *data =
new KisFilterStrokeStrategy::IdleBarrierData();
d->idleBarrierCookie = data->idleBarrierCookie();
image->addJob(d->currentStrokeId, data);
}
QRegion extraUpdateRegion(d->currentProcessRect);
extraUpdateRegion -= processRect;
......@@ -355,6 +368,8 @@ void KisFilterManager::finish()
d->reapplyAction->setText(i18n("Apply Filter Again: %1", filter->name()));
d->currentStrokeId.clear();
d->cancelSilentlyHandle.clear();
d->idleBarrierCookie.clear();
d->currentlyAppliedConfiguration.clear();
d->currentProcessRect = QRect();
}
......@@ -366,6 +381,8 @@ void KisFilterManager::cancel()
d->view->image()->cancelStroke(d->currentStrokeId);
d->currentStrokeId.clear();
d->cancelSilentlyHandle.clear();
d->idleBarrierCookie.clear();
d->currentlyAppliedConfiguration.clear();
d->currentProcessRect = QRect();
}
......@@ -375,6 +392,11 @@ bool KisFilterManager::isStrokeRunning() const
return d->currentStrokeId;
}
bool KisFilterManager::isIdle() const
{
return !d->idleBarrierCookie;
}
void KisFilterManager::slotStrokeEndRequested()
{
if (d->currentStrokeId && d->filterDialog) {
......
......@@ -39,6 +39,8 @@ public:
void cancel();
bool isStrokeRunning() const;
bool isIdle() const;
private Q_SLOTS:
void insertFilter(const QString &name);
......
......@@ -15,8 +15,8 @@
struct KisFilterStrokeStrategy::Private {
Private()
: updatesFacade(0),
cancelSilently(false),
secondaryTransaction(0),
cancelSilentlyHandle(new QAtomicInt()),
levelOfDetail(0)
{
}
......@@ -26,11 +26,11 @@ struct KisFilterStrokeStrategy::Private {
filterConfig(rhs.filterConfig),
node(rhs.node),
updatesFacade(rhs.updatesFacade),
cancelSilently(rhs.cancelSilently),
filterDevice(),
filterDeviceBounds(),
secondaryTransaction(0),
progressHelper(),
cancelSilentlyHandle(rhs.cancelSilentlyHandle),
levelOfDetail(0)
{
KIS_ASSERT_RECOVER_RETURN(!rhs.filterDevice);
......@@ -45,11 +45,11 @@ struct KisFilterStrokeStrategy::Private {
KisNodeSP node;
KisUpdatesFacade *updatesFacade;
bool cancelSilently;
KisPaintDeviceSP filterDevice;
QRect filterDeviceBounds;
KisTransaction *secondaryTransaction;
QScopedPointer<KisProcessingVisitor::ProgressHelper> progressHelper;
QSharedPointer<QAtomicInt> cancelSilentlyHandle;
int levelOfDetail;
};
......@@ -68,7 +68,6 @@ KisFilterStrokeStrategy::KisFilterStrokeStrategy(KisFilterSP filter,
m_d->filterConfig = filterConfig;
m_d->node = resources->currentNode();
m_d->updatesFacade = resources->image().data();
m_d->cancelSilently = false;
m_d->secondaryTransaction = 0;
m_d->levelOfDetail = 0;
......@@ -121,8 +120,6 @@ void KisFilterStrokeStrategy::initStrokeCallback()
void KisFilterStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast<Data*>(data);
CancelSilentlyMarker *cancelJob =
dynamic_cast<CancelSilentlyMarker*>(data);
ExtraCleanUpUpdates *cleanup = dynamic_cast<ExtraCleanUpUpdates*>(data);
if (d) {
......@@ -146,10 +143,10 @@ void KisFilterStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
}
m_d->node->setDirty(rc);
} else if (cancelJob) {
m_d->cancelSilently = true;
} else if (cleanup) {
m_d->node->setDirty(cleanup->rects);
} else if (dynamic_cast<IdleBarrierData*>(data)) {
/* noop, just delete that */
} else {
qFatal("KisFilterStrokeStrategy: job type is not known");
}
......@@ -160,13 +157,15 @@ void KisFilterStrokeStrategy::cancelStrokeCallback()
delete m_d->secondaryTransaction;
m_d->filterDevice = 0;
if (m_d->cancelSilently) {
const bool shouldCancelSilently = *m_d->cancelSilentlyHandle;
if (shouldCancelSilently) {
m_d->updatesFacade->disableDirtyRequests();
}
KisPainterBasedStrokeStrategy::cancelStrokeCallback();
if (m_d->cancelSilently) {
if (shouldCancelSilently) {
m_d->updatesFacade->enableDirtyRequests();
}
}
......@@ -187,3 +186,8 @@ KisStrokeStrategy* KisFilterStrokeStrategy::createLodClone(int levelOfDetail)
KisFilterStrokeStrategy *clone = new KisFilterStrokeStrategy(*this, levelOfDetail);
return clone;
}
QSharedPointer<QAtomicInt> KisFilterStrokeStrategy::cancelSilentlyHandle() const
{
return m_d->cancelSilentlyHandle;
}
......@@ -37,18 +37,37 @@ public:
};
class CancelSilentlyMarker : public KisStrokeJobData {
class IdleBarrierData : public KisStrokeJobData {
public:
CancelSilentlyMarker()
: KisStrokeJobData(SEQUENTIAL)
{}
IdleBarrierData()
: KisStrokeJobData(SEQUENTIAL),
m_idleBarrierCookie(new std::tuple<>())
{
}
KisStrokeJobData* createLodClone(int /*levelOfDetail*/) override {
return new CancelSilentlyMarker(*this);
KisStrokeJobData* createLodClone(int levelOfDetail) override {
return new IdleBarrierData(*this, levelOfDetail);
}
using IdleBarrierCookie = QWeakPointer<std::tuple<>>;
IdleBarrierCookie idleBarrierCookie() const {
return m_idleBarrierCookie;
}
};
private:
IdleBarrierData(IdleBarrierData &rhs, int levelOfDetail)
: KisStrokeJobData(rhs)
{
// the cookie is used for preview only, therefore in
// instant preview mode we pass it to the lodn stroke
rhs.m_idleBarrierCookie.swap(m_idleBarrierCookie);
}
QSharedPointer<std::tuple<>> m_idleBarrierCookie;
};
class ExtraCleanUpUpdates : public KisStrokeJobData {
public:
ExtraCleanUpUpdates(const QVector<QRect> &_rects)
......@@ -91,6 +110,8 @@ public:
KisStrokeStrategy* createLodClone(int levelOfDetail) override;
QSharedPointer<QAtomicInt> cancelSilentlyHandle() const;
private:
struct Private;
Private* const m_d;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment