Improve animation playback performance

We try to redraw only the necessary parts of the Timeline,
and when we are in playback mode, make bigger jumps
to avoid a continuos scroll in the Timeline.
This commit is contained in:
David Capello 2018-03-29 16:13:10 -03:00
parent 62ee2daaba
commit fac3574d02
4 changed files with 118 additions and 31 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -85,7 +85,7 @@ public:
m_previewDelegate->onPreviewOtherEditor(this);
if (isActive())
set_current_palette(editor->sprite()->palette(editor->frame()), true);
set_current_palette(editor->sprite()->palette(editor->frame()), false);
}
void onAfterLayerChanged(Editor* editor) override {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -212,7 +212,6 @@ void PlayState::onPlaybackTick()
m_editor->setFrame(frame);
m_nextFrameTime += getNextFrameTime();
m_editor->invalidate();
}
m_curFrameTick = base::current_tick();

View File

@ -314,7 +314,6 @@ void Timeline::updateUsingEditor(Editor* editor)
if (m_range.enabled() &&
m_rangeLocks == 0) {
m_range.clearRange();
invalidate();
}
// We always update the editor. In this way the timeline keeps in
@ -414,6 +413,9 @@ void Timeline::setLayer(Layer* layer)
{
ASSERT(m_editor != NULL);
invalidateLayer(m_layer);
invalidateLayer(layer);
m_layer = layer;
// Expand all parents
@ -425,9 +427,8 @@ void Timeline::setLayer(Layer* layer)
group = group->parent();
}
regenerateRows();
}
invalidate();
}
if (m_editor->layer() != layer)
m_editor->setLayer(m_layer);
@ -443,11 +444,20 @@ void Timeline::setFrame(frame_t frame, bool byUser)
else if (frame >= m_sprite->totalFrames())
frame = frame_t(m_sprite->totalFrames()-1);
invalidateFrame(m_frame);
invalidateFrame(frame);
gfx::Rect onionRc = getOnionskinFramesBounds();
m_frame = frame;
invalidate();
// Invalidate the onionskin handles area
onionRc |= getOnionskinFramesBounds();
if (!onionRc.isEmpty())
invalidateRect(onionRc.offset(origin()));
if (m_editor->frame() != frame) {
bool isPlaying = m_editor->isPlaying();
const bool isPlaying = m_editor->isPlaying();
if (isPlaying)
m_editor->stop();
@ -623,7 +633,7 @@ bool Timeline::onProcessMessage(Message* msg)
if (selectFrame) {
m_state = STATE_SELECTING_FRAMES;
if (clearRange)
m_range.clearRange();
clearAndInvalidateRange();
m_range.startRange(m_layer, m_clk.frame, Range::kFrames);
m_startRange = m_range;
@ -647,7 +657,7 @@ bool Timeline::onProcessMessage(Message* msg)
else if (selectLayer) {
m_state = STATE_SELECTING_LAYERS;
if (clearRange)
m_range.clearRange();
clearAndInvalidateRange();
m_range.startRange(m_rows[m_clk.layer].layer(),
m_frame, Range::kLayers);
m_startRange = m_range;
@ -689,7 +699,7 @@ bool Timeline::onProcessMessage(Message* msg)
else {
if (selectCel) {
m_state = STATE_SELECTING_CELS;
m_range.clearRange();
clearAndInvalidateRange();
m_range.startRange(m_rows[m_clk.layer].layer(),
m_clk.frame, Range::kCels);
m_startRange = m_range;
@ -775,18 +785,27 @@ bool Timeline::onProcessMessage(Message* msg)
}
case STATE_MOVING_ONIONSKIN_RANGE_LEFT: {
gfx::Rect onionRc = getOnionskinFramesBounds();
int newValue = m_origFrames + (m_clk.frame - hit.frame);
docPref().onionskin.prevFrames(MAX(0, newValue));
invalidate();
onionRc |= getOnionskinFramesBounds();
invalidateRect(onionRc.offset(origin()));
return true;
}
case STATE_MOVING_ONIONSKIN_RANGE_RIGHT:
case STATE_MOVING_ONIONSKIN_RANGE_RIGHT: {
gfx::Rect onionRc = getOnionskinFramesBounds();
int newValue = m_origFrames - (m_clk.frame - hit.frame);
docPref().onionskin.nextFrames(MAX(0, newValue));
invalidate();
onionRc |= getOnionskinFramesBounds();
invalidateRect(onionRc.offset(origin()));
return true;
}
}
// If the mouse pressed the mouse's button in the separator,
// we shouldn't change the hot (so the separator can be
@ -820,10 +839,15 @@ bool Timeline::onProcessMessage(Message* msg)
}
case STATE_SELECTING_FRAMES: {
if (m_range.enabled())
invalidateHit(Hit(PART_RANGE_OUTLINE));
m_range = m_startRange;
m_range.endRange(m_layer, hit.frame);
setFrame(m_clk.frame = hit.frame, true);
invalidateHit(Hit(PART_RANGE_OUTLINE));
break;
}
@ -1195,8 +1219,7 @@ bool Timeline::onProcessMessage(Message* msg)
case kKeyEsc:
if (m_state == STATE_STANDBY) {
m_range.clearRange();
invalidate();
clearAndInvalidateRange();
}
else {
m_state = STATE_STANDBY;
@ -1516,6 +1539,7 @@ void Timeline::onAfterCommandExecution(CommandExecutionEvent& ev)
if (!m_document)
return;
// TODO improve this: no need to regenerate everything after each command
regenerateRows();
showCurrentCel();
invalidate();
@ -1598,7 +1622,7 @@ void Timeline::onRemoveFrame(doc::DocumentEvent& ev)
// Disable the selected range when we remove frames
if (m_range.enabled())
m_range.clearRange();
clearAndInvalidateRange();
showCurrentCel();
clearClipboardRange();
@ -1608,8 +1632,7 @@ void Timeline::onRemoveFrame(doc::DocumentEvent& ev)
void Timeline::onSelectionChanged(doc::DocumentEvent& ev)
{
if (m_rangeLocks == 0)
m_range.clearRange();
invalidate();
clearAndInvalidateRange();
}
void Timeline::onLayerNameChange(doc::DocumentEvent& ev)
@ -1644,10 +1667,9 @@ void Timeline::onAfterFrameChanged(Editor* editor)
setFrame(editor->frame(), false);
if (!hasCapture())
m_range.clearRange();
clearAndInvalidateRange();
showCurrentCel();
invalidate();
}
void Timeline::onAfterLayerChanged(Editor* editor)
@ -1659,9 +1681,7 @@ void Timeline::onAfterLayerChanged(Editor* editor)
m_range.clearRange();
setLayer(editor->layer());
showCurrentCel();
invalidate();
}
void Timeline::onDestroyEditor(Editor* editor)
@ -2694,6 +2714,40 @@ void Timeline::invalidateHit(const Hit& hit)
invalidateRect(getPartBounds(hit).offset(origin()));
}
void Timeline::invalidateLayer(const Layer* layer)
{
if (layer == nullptr)
return;
layer_t layerIdx = getLayerIndex(layer);
if (layerIdx < firstLayer())
return;
gfx::Rect rc = getPartBounds(Hit(PART_ROW, layerIdx));
gfx::Rect rcCels;
rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, firstFrame()));
rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, lastFrame()));
rcCels &= getCelsBounds();
rc |= rcCels;
rc.offset(origin());
invalidateRect(rc);
}
void Timeline::invalidateFrame(const frame_t frame)
{
if (!validFrame(frame))
return;
gfx::Rect rc = getPartBounds(Hit(PART_HEADER_FRAME, -1, frame));
gfx::Rect rcCels;
rcCels |= getPartBounds(Hit(PART_CEL, firstLayer(), frame));
rcCels |= getPartBounds(Hit(PART_CEL, lastLayer(), frame));
rcCels &= getCelsBounds();
rc |= rcCels;
rc.offset(origin());
invalidateRect(rc);
}
void Timeline::regenerateRows()
{
ASSERT(m_document);
@ -3011,9 +3065,7 @@ void Timeline::setHot(const Hit& hit)
// If the part, layer or frame change.
if (m_hot != hit) {
// Invalidate the whole control.
if (m_state == STATE_MOVING_RANGE ||
hit.part == PART_RANGE_OUTLINE ||
m_hot.part == PART_RANGE_OUTLINE) {
if (isMovingCel()) {
invalidate();
}
// Invalidate the old and new 'hot' thing.
@ -3199,14 +3251,20 @@ void Timeline::showCel(layer_t layer, frame_t frame)
viewport.y + layerBoxHeight()*(lastLayer() - layer) - scroll.y,
frameBoxWidth(), layerBoxHeight());
const bool isPlaying = m_editor->isPlaying();
// Here we use <= instead of < to avoid jumping between this
// condition and the "else if" one when we are playing the
// animation.
if (celBounds.x <= viewport.x) {
scroll.x -= viewport.x - celBounds.x;
if (isPlaying)
scroll.x -= viewport.w/2 - celBounds.w/2;
}
else if (celBounds.x2() > viewport.x2()) {
scroll.x += celBounds.x2() - viewport.x2();
if (isPlaying)
scroll.x += viewport.w/2 - celBounds.w/2;
}
if (celBounds.y <= viewport.y) {
@ -3419,12 +3477,26 @@ void Timeline::setViewScroll(const gfx::Point& pt)
newScroll.x = MID(0, newScroll.x, maxPos.x);
newScroll.y = MID(0, newScroll.y, maxPos.y);
if (newScroll == oldScroll)
return;
if (newScroll.y != oldScroll.y) {
gfx::Rect rc;
rc |= getPartBounds(Hit(PART_ROW, 0));
rc |= getPartBounds(Hit(PART_ROW, lastLayer()));
rc.offset(origin());
invalidateRect(rc);
}
if (newScroll != oldScroll) {
gfx::Rect rc;
if (m_tagBands > 0)
rc |= getPartBounds(Hit(PART_FRAME_TAG_BAND));
rc |= getFrameHeadersBounds();
rc |= getCelsBounds();
rc.offset(origin());
invalidateRect(rc);
}
m_hbar.setPos(newScroll.x);
m_vbar.setPos(newScroll.y);
invalidate();
}
@ -3510,6 +3582,19 @@ void Timeline::clearClipboardRange()
m_clipboard_timer.stop();
}
void Timeline::clearAndInvalidateRange()
{
if (m_range.enabled()) {
for (const Layer* layer : m_range.selectedLayers())
invalidateLayer(layer);
for (const frame_t frame : m_range.selectedFrames())
invalidateFrame(frame);
invalidateHit(Hit(PART_RANGE_OUTLINE));
m_range.clearRange();
}
}
DocumentPreferences& Timeline::docPref() const
{
return Preferences::instance().document(m_document);
@ -3686,7 +3771,7 @@ bool Timeline::onClear(Context* ctx)
void Timeline::onCancel(Context* ctx)
{
if (m_rangeLocks == 0)
m_range.clearRange();
clearAndInvalidateRange();
clearClipboardRange();
invalidate();

View File

@ -262,6 +262,8 @@ namespace app {
gfx::Rect getPartBounds(const Hit& hit) const;
gfx::Rect getRangeBounds(const Range& range) const;
void invalidateHit(const Hit& hit);
void invalidateLayer(const Layer* layer);
void invalidateFrame(const frame_t frame);
void regenerateRows();
void regenerateTagBands();
int visibleTagBands() const;
@ -282,6 +284,7 @@ namespace app {
void updateStatusBar(ui::Message* msg);
void updateDropRange(const gfx::Point& pt);
void clearClipboardRange();
void clearAndInvalidateRange();
// The layer of the bottom (e.g. Background layer)
layer_t firstLayer() const { return 0; }