mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Fix multi player with same frame rates.
This commit is contained in:
parent
f6bfbbb805
commit
1da5d1c64f
5 changed files with 110 additions and 55 deletions
|
@ -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<Player*> owner, crl::time now) {
|
||||
void SharedState::start(
|
||||
not_null<Player*> 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<const Frame*> 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.");
|
||||
|
|
|
@ -57,14 +57,19 @@ public:
|
|||
std::unique_ptr<Cache> cache,
|
||||
const FrameRequest &request);
|
||||
|
||||
void start(not_null<Player*> owner, crl::time now);
|
||||
void start(
|
||||
not_null<Player*> owner,
|
||||
crl::time now,
|
||||
crl::time delay = 0,
|
||||
int skippedFrames = 0);
|
||||
|
||||
[[nodiscard]] Information information() const;
|
||||
[[nodiscard]] bool initialized() const;
|
||||
|
||||
[[nodiscard]] not_null<Frame*> 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<Frame*> getFrame(int index);
|
||||
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
|
||||
[[nodiscard]] int counter() const;
|
||||
[[nodiscard]] crl::time currentFramePosition() const;
|
||||
|
||||
QByteArray _content;
|
||||
std::unique_ptr<rlottie::Animation> _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<int> _counter = kCounterUninitialized;
|
||||
|
||||
|
@ -100,12 +108,16 @@ private:
|
|||
|
||||
base::weak_ptr<Player> _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<int> _accumulatedDelayMs = 0;
|
||||
|
||||
std::unique_ptr<Cache> _cache;
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
|
|||
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> 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<Animation*> MultiPlayer::append(
|
|||
return _animations.back().get();
|
||||
}
|
||||
|
||||
crl::time MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
|
||||
void MultiPlayer::startAtRightTime(not_null<SharedState*> 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<SharedState> 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*> animation,
|
||||
std::unique_ptr<SharedState> 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*> animation) {
|
||||
|
@ -112,7 +123,7 @@ void MultiPlayer::remove(not_null<Animation*> animation) {
|
|||
|
||||
if (_active.empty()) {
|
||||
_started = kTimeUnknown;
|
||||
_accumulatedDelay = 0;
|
||||
_delay = 0;
|
||||
_nextFrameTime = kTimeUnknown;
|
||||
_timer.cancel();
|
||||
}
|
||||
|
@ -127,7 +138,7 @@ rpl::producer<MultiUpdate> 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) {
|
||||
|
|
|
@ -56,8 +56,13 @@ public:
|
|||
void remove(not_null<Animation*> animation);
|
||||
|
||||
private:
|
||||
crl::time startAtRightTime(not_null<SharedState*> state);
|
||||
void markFrameDisplayed(crl::time now, crl::time delayed);
|
||||
void appendToActive(
|
||||
not_null<Animation*> animation,
|
||||
std::unique_ptr<SharedState> state);
|
||||
void startAtRightTime(not_null<SharedState*> 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<FrameRenderer> _renderer;
|
||||
std::vector<std::unique_ptr<Animation>> _animations;
|
||||
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
|
||||
base::flat_map<
|
||||
not_null<Animation*>,
|
||||
std::unique_ptr<SharedState>> _pendingToStart;
|
||||
//base::flat_map<not_null<Animation*>, not_null<SharedState*>> _paused;
|
||||
crl::time _started = kTimeUnknown;
|
||||
crl::time _accumulatedDelay = 0;
|
||||
crl::time _delay = 0;
|
||||
crl::time _nextFrameTime = kTimeUnknown;
|
||||
rpl::event_stream<MultiUpdate> _updates;
|
||||
rpl::lifetime _lifetime;
|
||||
|
|
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue