From 1da5d1c64f3a71703c2128a2aae283861ec3232c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Jun 2019 15:19:57 +0200 Subject: [PATCH] Fix multi player with same frame rates. --- .../lottie/lottie_frame_renderer.cpp | 45 +++++++---- .../lottie/lottie_frame_renderer.h | 20 ++++- .../lottie/lottie_multi_player.cpp | 78 ++++++++++++------- .../SourceFiles/lottie/lottie_multi_player.h | 14 +++- .../lottie/lottie_single_player.cpp | 8 +- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp index 95d43c48cc..cb7eaf11bc 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp @@ -298,8 +298,6 @@ void SharedState::renderFrame( void SharedState::init(QImage cover, const FrameRequest &request) { Expects(!initialized()); - _duration = crl::time(1000) * _framesCount / _frameRate; - _frames[0].request = request; _frames[0].original = std::move(cover); _frames[0].position = 0; @@ -307,13 +305,20 @@ void SharedState::init(QImage cover, const FrameRequest &request) { // Usually main thread sets displayed time before _counter increment. // But in this case we update _counter, so we set a fake displayed time. _frames[0].displayed = kDisplaySkipped; - - _counter.store(0, std::memory_order_release); } -void SharedState::start(not_null owner, crl::time now) { +void SharedState::start( + not_null owner, + crl::time started, + crl::time delay, + int skippedFrames) { _owner = owner; - _started = now; + _started = started; + _delay = delay; + _skippedFrames = skippedFrames; + + _frames[0].position = currentFramePosition(); + _counter.store(0, std::memory_order_release); } bool IsRendered(not_null frame) { @@ -328,10 +333,14 @@ void SharedState::renderNextFrame( renderFrame(frame->original, request, (++_frameIndex) % _framesCount); PrepareFrameByRequest(frame); - frame->position = crl::time(1000) * _frameIndex / _frameRate; + frame->position = currentFramePosition(); frame->displayed = kTimeUnknown; } +crl::time SharedState::currentFramePosition() const { + return crl::time(1000) * (_skippedFrames + _frameIndex) / _frameRate; +} + auto SharedState::renderNextFrame(const FrameRequest &request) -> RenderResult { const auto prerender = [&](int index) -> RenderResult { @@ -351,7 +360,7 @@ auto SharedState::renderNextFrame(const FrameRequest &request) if (!IsRendered(frame)) { renderNextFrame(frame, request); } - frame->display = _started + _accumulatedDelayMs + frame->position; + frame->display = _started + _delay + frame->position; // Release this frame to the main thread for rendering. _counter.store( @@ -441,7 +450,16 @@ crl::time SharedState::nextFrameDisplayTime() const { Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime."); } -crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) { +void SharedState::addTimelineDelay(crl::time delayed) { + if (!delayed) { + return; + } + + Assert(counter() % 2 == 1); + _delay += delayed; +} + +crl::time SharedState::markFrameDisplayed(crl::time now) { const auto mark = [&](int counter) { const auto next = (counter + 1) % (2 * kFramesCount); const auto index = next / 2; @@ -455,15 +473,14 @@ crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) { return frame->position; }; - _accumulatedDelayMs += delayed; switch (counter()) { - case 0: return kTimeUnknown; + case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed."); case 1: return mark(1); - case 2: return kTimeUnknown; + case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed."); case 3: return mark(3); - case 4: return kTimeUnknown; + case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed."); case 5: return mark(5); - case 6: return kTimeUnknown; + case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed."); case 7: return mark(7); } Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed."); diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h index 0d30e07b0b..efc3143ac9 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h @@ -57,14 +57,19 @@ public: std::unique_ptr cache, const FrameRequest &request); - void start(not_null owner, crl::time now); + void start( + not_null owner, + crl::time now, + crl::time delay = 0, + int skippedFrames = 0); [[nodiscard]] Information information() const; [[nodiscard]] bool initialized() const; [[nodiscard]] not_null frameForPaint(); [[nodiscard]] crl::time nextFrameDisplayTime() const; - crl::time markFrameDisplayed(crl::time now, crl::time delayed); + void addTimelineDelay(crl::time delayed); + crl::time markFrameDisplayed(crl::time now); crl::time markFrameShown(); void renderFrame(QImage &image, const FrameRequest &request, int index); @@ -88,10 +93,13 @@ private: [[nodiscard]] not_null getFrame(int index); [[nodiscard]] not_null getFrame(int index) const; [[nodiscard]] int counter() const; + [[nodiscard]] crl::time currentFramePosition() const; QByteArray _content; std::unique_ptr _animation; + // crl::queue changes 0,2,4,6 to 1,3,5,7. + // main thread changes 1,3,5,7 to 2,4,6,0. static constexpr auto kCounterUninitialized = -1; std::atomic _counter = kCounterUninitialized; @@ -100,12 +108,16 @@ private: base::weak_ptr _owner; crl::time _started = kTimeUnknown; - crl::time _duration = kTimeUnknown; + + // (_counter % 2) == 1 main thread can write _delay. + // (_counter % 2) == 0 crl::queue can read _delay. + crl::time _delay = kTimeUnknown; + int _frameIndex = 0; + int _skippedFrames = 0; int _framesCount = 0; int _frameRate = 0; QSize _size; - std::atomic _accumulatedDelayMs = 0; std::unique_ptr _cache; diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp b/Telegram/SourceFiles/lottie/lottie_multi_player.cpp index 914ba89b11..f0b5d1d41d 100644 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_multi_player.cpp @@ -22,6 +22,10 @@ std::shared_ptr MakeFrameRenderer() { MultiPlayer::MultiPlayer(std::shared_ptr renderer) : _timer([=] { checkNextFrameRender(); }) , _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) { + crl::on_main_update_requests( + ) | rpl::start_with_next([=] { + checkStep(); + }, _lifetime); } MultiPlayer::~MultiPlayer() { @@ -54,26 +58,22 @@ not_null MultiPlayer::append( return _animations.back().get(); } -crl::time MultiPlayer::startAtRightTime(not_null state) { +void MultiPlayer::startAtRightTime(not_null state) { Expects(!_active.empty()); Expects((_active.size() == 1) == (_started == kTimeUnknown)); + const auto now = crl::now(); if (_active.size() == 1) { - _started = crl::now(); - state->start(this, _started); - return _started; + _started = now; } - const auto now = crl::now(); const auto rate = state->information().frameRate; Assert(rate != 0); - const auto started = _started + _accumulatedDelay; + const auto started = _started + _delay; const auto skipFrames = (now - started) * rate / 1000; - const auto startAt = started + (1000 * skipFrames / rate); - state->start(this, startAt); - return startAt; + state->start(this, _started, _delay, skipFrames); } void MultiPlayer::start( @@ -81,21 +81,32 @@ void MultiPlayer::start( std::unique_ptr state) { Expects(state != nullptr); + if (_nextFrameTime == kTimeUnknown) { + appendToActive(animation, std::move(state)); + } else { + // We always try to mark as shown at the same time, so we start a new + // animation at the same time we mark all existing as shown. + _pendingToStart.emplace(animation, std::move(state)); + } +} + +void MultiPlayer::appendPendingToActive() { + for (auto &[animation, state] : base::take(_pendingToStart)) { + appendToActive(animation, std::move(state)); + } +} + +void MultiPlayer::appendToActive( + not_null animation, + std::unique_ptr state) { + Expects(_nextFrameTime == kTimeUnknown); + _active.emplace(animation, state.get()); auto information = state->information(); startAtRightTime(state.get()); _renderer->append(std::move(state)); _updates.fire({}); - - crl::on_main_update_requests( - ) | rpl::start_with_next([=] { - checkStep(); - }, _lifetime); - - _nextFrameTime = kTimeUnknown; - _timer.cancel(); - checkStep(); } void MultiPlayer::remove(not_null animation) { @@ -112,7 +123,7 @@ void MultiPlayer::remove(not_null animation) { if (_active.empty()) { _started = kTimeUnknown; - _accumulatedDelay = 0; + _delay = 0; _nextFrameTime = kTimeUnknown; _timer.cancel(); } @@ -127,7 +138,7 @@ rpl::producer MultiPlayer::updates() const { } void MultiPlayer::checkStep() { - if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { + if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) { return; } else if (_nextFrameTime != kTimeUnknown) { checkNextFrameRender(); @@ -139,9 +150,6 @@ void MultiPlayer::checkStep() { void MultiPlayer::checkNextFrameAvailability() { Expects(_nextFrameTime == kTimeUnknown); - if (_active.empty()) { - return; - } auto next = kTimeUnknown; for (const auto &[animation, state] : _active) { const auto time = state->nextFrameDisplayTime(); @@ -181,10 +189,10 @@ void MultiPlayer::checkNextFrameRender() { } else { _timer.cancel(); - const auto exact = std::exchange( - _nextFrameTime, - kFrameDisplayTimeAlreadyDone); - markFrameDisplayed(now, now - exact); + markFrameDisplayed(now); + addTimelineDelay(now - _nextFrameTime); + + _nextFrameTime = kFrameDisplayTimeAlreadyDone; _updates.fire({}); } } @@ -198,7 +206,7 @@ void MultiPlayer::updateFrameRequest( _renderer->updateFrameRequest(i->second, request); } -void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) { +void MultiPlayer::markFrameDisplayed(crl::time now) { Expects(!_active.empty()); auto displayed = 0; @@ -210,17 +218,27 @@ void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) { continue; } else if (now >= time) { ++displayed; - state->markFrameDisplayed(now, delayed); + state->markFrameDisplayed(now); } else { ++waiting; } } - PROFILE_LOG(("PLAYER FRAME DISPLAYED AT: %1, DELAYED: %2, (MARKED %3, WAITING %4)").arg(now).arg(delayed).arg(displayed).arg(waiting)); + PROFILE_LOG(("PLAYER FRAME DISPLAYED AT: %1 (MARKED %2, WAITING %3)").arg(now).arg(displayed).arg(waiting)); +} + +void MultiPlayer::addTimelineDelay(crl::time delayed) { + Expects(!_active.empty()); + + for (const auto &[animation, state] : _active) { + state->addTimelineDelay(delayed); + } + _delay += delayed; } void MultiPlayer::markFrameShown() { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { _nextFrameTime = kTimeUnknown; + appendPendingToActive(); } auto count = 0; for (const auto &[animation, state] : _active) { diff --git a/Telegram/SourceFiles/lottie/lottie_multi_player.h b/Telegram/SourceFiles/lottie/lottie_multi_player.h index 6a19a34a8a..53be168519 100644 --- a/Telegram/SourceFiles/lottie/lottie_multi_player.h +++ b/Telegram/SourceFiles/lottie/lottie_multi_player.h @@ -56,8 +56,13 @@ public: void remove(not_null animation); private: - crl::time startAtRightTime(not_null state); - void markFrameDisplayed(crl::time now, crl::time delayed); + void appendToActive( + not_null animation, + std::unique_ptr state); + void startAtRightTime(not_null state); + void appendPendingToActive(); + void markFrameDisplayed(crl::time now); + void addTimelineDelay(crl::time delayed); void checkNextFrameAvailability(); void checkNextFrameRender(); @@ -65,9 +70,12 @@ private: const std::shared_ptr _renderer; std::vector> _animations; base::flat_map, not_null> _active; + base::flat_map< + not_null, + std::unique_ptr> _pendingToStart; //base::flat_map, not_null> _paused; crl::time _started = kTimeUnknown; - crl::time _accumulatedDelay = 0; + crl::time _delay = 0; crl::time _nextFrameTime = kTimeUnknown; rpl::event_stream _updates; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/lottie/lottie_single_player.cpp b/Telegram/SourceFiles/lottie/lottie_single_player.cpp index f91b42e717..c7cc5e071c 100644 --- a/Telegram/SourceFiles/lottie/lottie_single_player.cpp +++ b/Telegram/SourceFiles/lottie/lottie_single_player.cpp @@ -102,10 +102,10 @@ void SinglePlayer::checkNextFrameRender() { } else { _timer.cancel(); - const auto exact = std::exchange( - _nextFrameTime, - kFrameDisplayTimeAlreadyDone); - const auto position = _state->markFrameDisplayed(now, now - exact); + const auto position = _state->markFrameDisplayed(now); + _state->addTimelineDelay(now - _nextFrameTime); + + _nextFrameTime = kFrameDisplayTimeAlreadyDone; _updates.fire({ DisplayFrameRequest{ position } }); } }