Improve filters preview performance (fix #1400)

Now several filter steps are applied in a background thread and a UI
timer refresh several rows in screen at the same time (instead of one
row per time). (This is something in my personal to-do list from long
time ago too.)
This commit is contained in:
David Capello 2017-01-31 09:59:09 -03:00
parent fbb0cdcbe2
commit f381cb972c
6 changed files with 79 additions and 28 deletions

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -110,7 +110,7 @@ void FilterManagerImpl::beginForPreview()
m_previewMask->replace(m_site.sprite()->bounds()); m_previewMask->replace(m_site.sprite()->bounds());
} }
m_row = 0; m_row = m_nextRowToFlush = 0;
m_mask = m_previewMask; m_mask = m_previewMask;
{ {
@ -257,17 +257,24 @@ void FilterManagerImpl::applyToTarget()
void FilterManagerImpl::flush() void FilterManagerImpl::flush()
{ {
if (m_row >= 0) { int h = m_row - m_nextRowToFlush;
if (m_row >= 0 && h > 0) {
Editor* editor = current_editor; Editor* editor = current_editor;
// We expand the region one pixel at the top and bottom of the
// region [m_row,m_nextRowToFlush) to be updated on the screen to
// avoid screen artifacts when we apply filters like convolution
// matrices.
gfx::Rect rect( gfx::Rect rect(
editor->editorToScreen( editor->editorToScreen(
gfx::Point( gfx::Point(
m_bounds.x, m_bounds.x,
m_bounds.y+m_row-1)), m_bounds.y+m_nextRowToFlush-1)),
gfx::Size( gfx::Size(
editor->projection().applyX(m_bounds.w), editor->projection().applyX(m_bounds.w),
(editor->projection().scaleY() >= 1 ? editor->projection().applyY(1): (editor->projection().scaleY() >= 1 ? editor->projection().applyY(h+2):
editor->projection().removeY(1)))); editor->projection().removeY(h+2))));
gfx::Region reg1(rect); gfx::Region reg1(rect);
gfx::Region reg2; gfx::Region reg2;
@ -275,6 +282,7 @@ void FilterManagerImpl::flush()
reg1.createIntersection(reg1, reg2); reg1.createIntersection(reg1, reg2);
editor->invalidateRegion(reg1); editor->invalidateRegion(reg1);
m_nextRowToFlush = m_row+1;
} }
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -120,6 +120,7 @@ namespace app {
doc::ImageRef m_src; doc::ImageRef m_src;
doc::ImageRef m_dst; doc::ImageRef m_dst;
int m_row; int m_row;
int m_nextRowToFlush;
gfx::Rect m_bounds; gfx::Rect m_bounds;
doc::Mask* m_mask; doc::Mask* m_mask;
base::UniquePtr<doc::Mask> m_previewMask; base::UniquePtr<doc::Mask> m_previewMask;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -13,6 +13,8 @@
#include "app/commands/filters/filter_manager_impl.h" #include "app/commands/filters/filter_manager_impl.h"
#include "app/modules/editors.h" #include "app/modules/editors.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "base/bind.h"
#include "base/scoped_lock.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "ui/manager.h" #include "ui/manager.h"
@ -28,6 +30,7 @@ FilterPreview::FilterPreview(FilterManagerImpl* filterMgr)
: Widget(kGenericWidget) : Widget(kGenericWidget)
, m_filterMgr(filterMgr) , m_filterMgr(filterMgr)
, m_timer(1, this) , m_timer(1, this)
, m_filterThread(nullptr)
{ {
setVisible(false); setVisible(false);
} }
@ -39,25 +42,32 @@ FilterPreview::~FilterPreview()
void FilterPreview::stop() void FilterPreview::stop()
{ {
if (m_timer.isRunning()) { {
ASSERT(m_filterMgr != NULL); base::scoped_lock lock(m_filterMgrMutex);
if (m_timer.isRunning()) {
m_filterMgr->end(); ASSERT(m_filterMgr);
m_filterMgr->end();
}
} }
m_filterMgr = NULL;
m_timer.stop(); m_timer.stop();
if (m_filterThread) {
m_filterThread->join();
m_filterThread.reset();
}
} }
void FilterPreview::restartPreview() void FilterPreview::restartPreview()
{ {
m_filterMgr->beginForPreview(); base::scoped_lock lock(m_filterMgrMutex);
m_timer.start();
}
FilterManagerImpl* FilterPreview::getFilterManager() const m_filterMgr->beginForPreview();
{ m_filterIsDone = false;
return m_filterMgr; m_filterThread.reset(new base::thread(
base::Bind<void>(&FilterPreview::onFilterThread, this)));
m_timer.start();
} }
bool FilterPreview::onProcessMessage(Message* msg) bool FilterPreview::onProcessMessage(Message* msg)
@ -80,17 +90,30 @@ bool FilterPreview::onProcessMessage(Message* msg)
m_timer.stop(); m_timer.stop();
break; break;
case kTimerMessage: case kTimerMessage: {
base::scoped_lock lock(m_filterMgrMutex);
if (m_filterMgr) { if (m_filterMgr) {
if (m_filterMgr->applyStep()) m_filterMgr->flush();
m_filterMgr->flush(); if (m_filterIsDone)
else
m_timer.stop(); m_timer.stop();
} }
break; break;
}
} }
return Widget::onProcessMessage(msg); return Widget::onProcessMessage(msg);
} }
// This is executed in other thread.
void FilterPreview::onFilterThread()
{
while (!m_filterIsDone && m_timer.isRunning()) {
{
base::scoped_lock lock(m_filterMgrMutex);
m_filterIsDone = !m_filterMgr->applyStep();
}
base::this_thread::yield();
}
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2015 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -8,6 +8,9 @@
#define APP_COMMANDS_FILTERS_FILTER_PREVIEW_H_INCLUDED #define APP_COMMANDS_FILTERS_FILTER_PREVIEW_H_INCLUDED
#pragma once #pragma once
#include "base/mutex.h"
#include "base/thread.h"
#include "base/unique_ptr.h"
#include "ui/timer.h" #include "ui/timer.h"
#include "ui/widget.h" #include "ui/widget.h"
@ -23,14 +26,18 @@ namespace app {
void stop(); void stop();
void restartPreview(); void restartPreview();
FilterManagerImpl* getFilterManager() const;
protected: protected:
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;
private: private:
void onFilterThread();
FilterManagerImpl* m_filterMgr; FilterManagerImpl* m_filterMgr;
ui::Timer m_timer; ui::Timer m_timer;
base::mutex m_filterMgrMutex;
base::UniquePtr<base::thread> m_filterThread;
bool m_filterIsDone;
}; };
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2015 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -121,6 +121,8 @@ void FilterWindow::restartPreview()
void FilterWindow::setNewTarget(Target target) void FilterWindow::setNewTarget(Target target)
{ {
stopPreview();
m_filterMgr->setTarget(target); m_filterMgr->setTarget(target);
m_targetButton.setTarget(target); m_targetButton.setTarget(target);
} }
@ -143,6 +145,8 @@ void FilterWindow::onShowPreview(Event& ev)
// Called when the user changes the target-buttons. // Called when the user changes the target-buttons.
void FilterWindow::onTargetButtonChange() void FilterWindow::onTargetButtonChange()
{ {
stopPreview();
// Change the targets in the filter manager and restart the filter preview. // Change the targets in the filter manager and restart the filter preview.
m_filterMgr->setTarget(m_targetButton.getTarget()); m_filterMgr->setTarget(m_targetButton.getTarget());
restartPreview(); restartPreview();
@ -150,7 +154,8 @@ void FilterWindow::onTargetButtonChange()
void FilterWindow::onTiledChange() void FilterWindow::onTiledChange()
{ {
ASSERT(m_tiledCheck != NULL); ASSERT(m_tiledCheck);
stopPreview();
// Call derived class implementation of setupTiledMode() so the // Call derived class implementation of setupTiledMode() so the
// filter is modified. // filter is modified.
@ -162,4 +167,9 @@ void FilterWindow::onTiledChange()
restartPreview(); restartPreview();
} }
void FilterWindow::stopPreview()
{
m_preview.stop();
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2015 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -62,6 +62,8 @@ namespace app {
virtual void setupTiledMode(TiledMode tiledMode) { } virtual void setupTiledMode(TiledMode tiledMode) { }
private: private:
void stopPreview();
const char* m_cfgSection; const char* m_cfgSection;
FilterManagerImpl* m_filterMgr; FilterManagerImpl* m_filterMgr;
ui::Box m_hbox; ui::Box m_hbox;