From d8768614310c2d80d51e277ff8b1e2feb11442d6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 1 Jan 2016 04:07:41 +0800 Subject: [PATCH] animations rewritten on atomics --- Telegram/SourceFiles/gui/animation.cpp | 142 +++++++++++++++++-------- Telegram/SourceFiles/gui/animation.h | 44 ++++++-- Telegram/SourceFiles/mediaview.cpp | 14 +-- 3 files changed, 136 insertions(+), 64 deletions(-) diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index c878d1eae..54c70a20e 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -225,10 +225,8 @@ ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Cal , _state(ClipReading) , _width(0) , _height(0) -, _currentDisplayed(1) +, _step(FirstFrameNotReadStep) , _paused(0) -, _lastDisplayMs(0) -, _startDisplayMs(getms()) , _autoplay(false) , _private(0) { if (_clipThreads.size() < ClipThreadsCount) { @@ -250,6 +248,48 @@ ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Cal _clipManagers.at(_threadIndex)->append(this, location, data); } +ClipReader::Frame *ClipReader::frameToShow() const { // 0 means not ready + int32 step = _step.loadAcquire(); + if (step == FirstFrameNotReadStep) { + return 0; + } else if (step == WaitingForRequestStep) { + return _frames; + } + return _frames + (((step + 1) / 2) % 3); +} + +ClipReader::Frame *ClipReader::frameToWrite() const { // 0 means not ready + int32 step = _step.loadAcquire(); + if (step == FirstFrameNotReadStep) { + return _frames; + } else if (step == WaitingForRequestStep) { + return 0; + } + return _frames + (((step + 3) / 2) % 3); +} + +ClipReader::Frame *ClipReader::frameToRequestOther() const { + int32 step = _step.loadAcquire(); + if (step == FirstFrameNotReadStep || step == WaitingForRequestStep) { + return 0; + } + return _frames + (((step + 5) / 2) % 3); +} + +void ClipReader::moveToNextShow() const { + int32 step = _step.loadAcquire(); + if (step % 2) { + _step.storeRelease((step + 1) % 6); + } +} + +void ClipReader::moveToNextWrite() const { + int32 step = _step.loadAcquire(); + if (!(step % 2)) { + _step.storeRelease(step + 1); + } +} + void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { // check if reader is not deleted already if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { @@ -261,20 +301,28 @@ void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, b if (_clipManagers.size() <= _threadIndex) error(); if (_state == ClipError) return; - int32 factor(cIntRetinaFactor()); - _request.factor = factor; - _request.framew = framew * factor; - _request.frameh = frameh * factor; - _request.outerw = outerw * factor; - _request.outerh = outerh * factor; - _request.rounded = rounded; - _clipManagers.at(_threadIndex)->start(this); + if (_step.loadAcquire() == WaitingForRequestStep) { + int32 factor(cIntRetinaFactor()); + ClipFrameRequest request; + request.factor = factor; + request.framew = framew * factor; + request.frameh = frameh * factor; + request.outerw = outerw * factor; + request.outerh = outerh * factor; + request.rounded = rounded; + _frames[0].request = _frames[1].request = _frames[2].request = request; + _step.storeRelease(0); // start working + _clipManagers.at(_threadIndex)->start(this); + } } QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { - _currentDisplayed.store(1); + Frame *frame = frameToShow(); + t_assert(frame != 0); + + frame->displayed = true; if (ms) { - _lastDisplayMs.storeRelease(int32(ms - _startDisplayMs.get())); + frame->when = ms; if (_paused.loadAcquire()) { _paused.storeRelease(0); if (_clipManagers.size() <= _threadIndex) error(); @@ -285,39 +333,44 @@ QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 oute } int32 factor(cIntRetinaFactor()); - QPixmap result(_current); - if (result.width() == outerw * factor && result.height() == outerh * factor) { - return result; + if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { + moveToNextShow(); + return frame->pix; } - _request.framew = framew * factor; - _request.frameh = frameh * factor; - _request.outerw = outerw * factor; - _request.outerh = outerh * factor; + frame->request.framew = framew * factor; + frame->request.frameh = frameh * factor; + frame->request.outerw = outerw * factor; + frame->request.outerh = outerh * factor; + frame->pix = QPixmap(); - QImage current(_currentOriginal); - current.setDevicePixelRatio(cRetinaFactor()); + QImage cache; + frame->pix = _prepareFrame(frame->request, frame->original, cache, true); - result = _current = QPixmap(); - result = _current = _prepareFrame(_request, current, _cacheForResize, true); + Frame *other = frameToRequestOther(); + t_assert(other != 0); + other->request = frame->request; + + moveToNextShow(); if (_clipManagers.size() <= _threadIndex) error(); if (_state != ClipError) { _clipManagers.at(_threadIndex)->update(this); } - return result; + return frame->pix; } bool ClipReader::ready() const { if (_width && _height) return true; - QImage first(_currentOriginal); - if (first.isNull()) return false; - - _width = first.width(); - _height = first.height(); - return true; + const Frame *frame = frameToShow(); + if (frame) { + _width = frame->original.width(); + _height = frame->original.height(); + return true; + } + return false; } int32 ClipReader::width() const { @@ -1003,25 +1056,21 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); } if (!reader->_paused && (result == ClipProcessRepaint || result == ClipProcessWait)) { - int32 lastDisplay = it.key()->_lastDisplayMs.loadAcquire(); - if (lastDisplay > 3000 || lastDisplay < 0) { // playing more then a day - it.key()->_startDisplayMs.set(ms); - it.key()->_lastDisplayMs.storeRelease(getms() - ms); - } else if (it.key()->_startDisplayMs.get() + lastDisplay + WaitBeforeGifPause < qMax(reader->_previousMs, ms)) { + ClipReader::Frame *frame = it.key()->frameToWrite(), *other = it.key()->frameToRequestOther(); + t_assert(frame != 0 && other != 0); + if (qMax(frame->when, other->when) + WaitBeforeGifPause < qMax(reader->_previousMs, ms)) { reader->_paused = true; it.key()->_paused.storeRelease(true); - if (it.key()->_startDisplayMs.get() + it.key()->_lastDisplayMs.loadAcquire() + WaitBeforeGifPause >= qMax(reader->_previousMs, ms)) { - it.key()->_paused.storeRelease(false); - reader->_paused = false; - } else { - result = ClipProcessReinit; - } + result = ClipProcessReinit; } } if (result == ClipProcessReinit || result == ClipProcessRepaint || result == ClipProcessStarted) { - it.key()->_current = reader->_current; - it.key()->_currentOriginal = reader->_currentOriginal; - it.key()->_currentDisplayed.store(false); + ClipReader::Frame *frame = it.key()->frameToWrite(); + t_assert(frame != 0); + frame->pix = reader->_current; + frame->original = reader->_currentOriginal; + frame->displayed = false; + it.key()->moveToNextWrite(); if (result == ClipProcessReinit) { emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); } else if (result == ClipProcessRepaint) { @@ -1073,7 +1122,8 @@ void ClipReadManager::process() { it.key()->_paused = false; } } - i.value()->_request = i.key()->_request; + ClipReader::Frame *frame = i.key()->frameToWrite(); + if (frame) i.value()->_request = frame->request; i.value() = 0; } } diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 571671488..729848209 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -27,7 +27,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org namespace anim { typedef float64 (*transition)(const float64 &delta, const float64 &dt); - + float64 linear(const float64 &delta, const float64 &dt); float64 sineInOut(const float64 &delta, const float64 &dt); float64 halfSine(const float64 &delta, const float64 &dt); @@ -520,6 +520,11 @@ enum ClipReaderNotification { ClipReaderRepaint, }; +enum ClipReaderSteps { + FirstFrameNotReadStep = -2, + WaitingForRequestStep = -1, +}; + class ClipReaderPrivate; class ClipReader { public: @@ -538,11 +543,16 @@ public: void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms); - QImage frameOriginal() const { - return _currentOriginal; + QPixmap frameOriginal() const { + Frame *frame = frameToShow(); + if (!frame) return QPixmap(); + QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); + result.detach(); + return result; } bool currentDisplayed() const { - return _currentDisplayed.load(); + Frame *frame = frameToShow(); + return frame ? frame->displayed : true; } bool paused() const { return _paused.loadAcquire(); @@ -556,7 +566,7 @@ public: ClipState state() const; bool started() const { - return _request.valid(); + return _step.loadAcquire() >= 0; } bool ready() const; @@ -571,14 +581,26 @@ private: ClipState _state; - ClipFrameRequest _request; - mutable int32 _width, _height; - QPixmap _current; - QImage _currentOriginal, _cacheForResize; - QAtomicInt _currentDisplayed, _paused, _lastDisplayMs; - Atomic _startDisplayMs; + mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 + struct Frame { + Frame() : displayed(false), when(0) { + } + QPixmap pix; + QImage original; + ClipFrameRequest request; + bool displayed; + uint64 when; + }; + mutable Frame _frames[3]; + Frame *frameToShow() const; // 0 means not ready + Frame *frameToWrite() const; // 0 means not ready + Frame *frameToRequestOther() const; + void moveToNextShow() const; + void moveToNextWrite() const; + + QAtomicInt _paused; int32 _threadIndex; bool _autoplay; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 8e45ef1aa..6100eb5a6 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -135,7 +135,7 @@ MediaView::MediaView() : TWidget(App::wnd()) hide(); createWinId(); - + _saveMsgUpdater.setSingleShot(true); connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage())); @@ -183,7 +183,7 @@ void MediaView::moveToScreen() { if (avail != geometry()) { setGeometry(avail); } - + int32 navSkip = 2 * st::mvControlMargin + st::mvControlSize; _closeNav = myrtlrect(width() - st::mvControlMargin - st::mvControlSize, st::mvControlMargin, st::mvControlSize, st::mvControlSize); _closeNavIcon = centersprite(_closeNav, st::mvClose); @@ -738,7 +738,7 @@ void MediaView::onCopy() { if (!_current.isNull()) { QApplication::clipboard()->setPixmap(_current); } else if (gifShown()) { - QApplication::clipboard()->setPixmap(QPixmap::fromImage(_gif->frameOriginal())); + QApplication::clipboard()->setPixmap(_gif->frameOriginal()); } } else { if (!_photo || !_photo->loaded()) return; @@ -1515,8 +1515,8 @@ void MediaView::moveToNext(int32 delta) { if (HistoryMedia *media = item->getMedia()) { switch (media->type()) { case MediaTypePhoto: displayPhoto(static_cast(item->getMedia())->photo(), item); preloadData(delta); break; - case MediaTypeDocument: - case MediaTypeGif: + case MediaTypeDocument: + case MediaTypeGif: case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break; } } else { @@ -1562,8 +1562,8 @@ void MediaView::preloadData(int32 delta) { if (HistoryMedia *media = item->getMedia()) { switch (media->type()) { case MediaTypePhoto: static_cast(media)->photo()->forget(); break; - case MediaTypeDocument: - case MediaTypeGif: + case MediaTypeDocument: + case MediaTypeGif: case MediaTypeSticker: media->getDocument()->forget(); break; } }