mirror of https://github.com/procxx/kepka.git
Fix multi player with same frame rates.
This commit is contained in:
parent
f6bfbbb805
commit
1da5d1c64f
|
@ -298,8 +298,6 @@ void SharedState::renderFrame(
|
||||||
void SharedState::init(QImage cover, const FrameRequest &request) {
|
void SharedState::init(QImage cover, const FrameRequest &request) {
|
||||||
Expects(!initialized());
|
Expects(!initialized());
|
||||||
|
|
||||||
_duration = crl::time(1000) * _framesCount / _frameRate;
|
|
||||||
|
|
||||||
_frames[0].request = request;
|
_frames[0].request = request;
|
||||||
_frames[0].original = std::move(cover);
|
_frames[0].original = std::move(cover);
|
||||||
_frames[0].position = 0;
|
_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.
|
// Usually main thread sets displayed time before _counter increment.
|
||||||
// But in this case we update _counter, so we set a fake displayed time.
|
// But in this case we update _counter, so we set a fake displayed time.
|
||||||
_frames[0].displayed = kDisplaySkipped;
|
_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;
|
_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) {
|
bool IsRendered(not_null<const Frame*> frame) {
|
||||||
|
@ -328,10 +333,14 @@ void SharedState::renderNextFrame(
|
||||||
|
|
||||||
renderFrame(frame->original, request, (++_frameIndex) % _framesCount);
|
renderFrame(frame->original, request, (++_frameIndex) % _framesCount);
|
||||||
PrepareFrameByRequest(frame);
|
PrepareFrameByRequest(frame);
|
||||||
frame->position = crl::time(1000) * _frameIndex / _frameRate;
|
frame->position = currentFramePosition();
|
||||||
frame->displayed = kTimeUnknown;
|
frame->displayed = kTimeUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crl::time SharedState::currentFramePosition() const {
|
||||||
|
return crl::time(1000) * (_skippedFrames + _frameIndex) / _frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
auto SharedState::renderNextFrame(const FrameRequest &request)
|
auto SharedState::renderNextFrame(const FrameRequest &request)
|
||||||
-> RenderResult {
|
-> RenderResult {
|
||||||
const auto prerender = [&](int index) -> RenderResult {
|
const auto prerender = [&](int index) -> RenderResult {
|
||||||
|
@ -351,7 +360,7 @@ auto SharedState::renderNextFrame(const FrameRequest &request)
|
||||||
if (!IsRendered(frame)) {
|
if (!IsRendered(frame)) {
|
||||||
renderNextFrame(frame, request);
|
renderNextFrame(frame, request);
|
||||||
}
|
}
|
||||||
frame->display = _started + _accumulatedDelayMs + frame->position;
|
frame->display = _started + _delay + frame->position;
|
||||||
|
|
||||||
// Release this frame to the main thread for rendering.
|
// Release this frame to the main thread for rendering.
|
||||||
_counter.store(
|
_counter.store(
|
||||||
|
@ -441,7 +450,16 @@ crl::time SharedState::nextFrameDisplayTime() const {
|
||||||
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
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 mark = [&](int counter) {
|
||||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||||
const auto index = next / 2;
|
const auto index = next / 2;
|
||||||
|
@ -455,15 +473,14 @@ crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) {
|
||||||
return frame->position;
|
return frame->position;
|
||||||
};
|
};
|
||||||
|
|
||||||
_accumulatedDelayMs += delayed;
|
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
case 0: return kTimeUnknown;
|
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
|
||||||
case 1: return mark(1);
|
case 1: return mark(1);
|
||||||
case 2: return kTimeUnknown;
|
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
|
||||||
case 3: return mark(3);
|
case 3: return mark(3);
|
||||||
case 4: return kTimeUnknown;
|
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
|
||||||
case 5: return mark(5);
|
case 5: return mark(5);
|
||||||
case 6: return kTimeUnknown;
|
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
|
||||||
case 7: return mark(7);
|
case 7: return mark(7);
|
||||||
}
|
}
|
||||||
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
||||||
|
|
|
@ -57,14 +57,19 @@ public:
|
||||||
std::unique_ptr<Cache> cache,
|
std::unique_ptr<Cache> cache,
|
||||||
const FrameRequest &request);
|
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]] Information information() const;
|
||||||
[[nodiscard]] bool initialized() const;
|
[[nodiscard]] bool initialized() const;
|
||||||
|
|
||||||
[[nodiscard]] not_null<Frame*> frameForPaint();
|
[[nodiscard]] not_null<Frame*> frameForPaint();
|
||||||
[[nodiscard]] crl::time nextFrameDisplayTime() const;
|
[[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();
|
crl::time markFrameShown();
|
||||||
|
|
||||||
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
void renderFrame(QImage &image, const FrameRequest &request, int index);
|
||||||
|
@ -88,10 +93,13 @@ private:
|
||||||
[[nodiscard]] not_null<Frame*> getFrame(int index);
|
[[nodiscard]] not_null<Frame*> getFrame(int index);
|
||||||
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
|
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
|
||||||
[[nodiscard]] int counter() const;
|
[[nodiscard]] int counter() const;
|
||||||
|
[[nodiscard]] crl::time currentFramePosition() const;
|
||||||
|
|
||||||
QByteArray _content;
|
QByteArray _content;
|
||||||
std::unique_ptr<rlottie::Animation> _animation;
|
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;
|
static constexpr auto kCounterUninitialized = -1;
|
||||||
std::atomic<int> _counter = kCounterUninitialized;
|
std::atomic<int> _counter = kCounterUninitialized;
|
||||||
|
|
||||||
|
@ -100,12 +108,16 @@ private:
|
||||||
|
|
||||||
base::weak_ptr<Player> _owner;
|
base::weak_ptr<Player> _owner;
|
||||||
crl::time _started = kTimeUnknown;
|
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 _frameIndex = 0;
|
||||||
|
int _skippedFrames = 0;
|
||||||
int _framesCount = 0;
|
int _framesCount = 0;
|
||||||
int _frameRate = 0;
|
int _frameRate = 0;
|
||||||
QSize _size;
|
QSize _size;
|
||||||
std::atomic<int> _accumulatedDelayMs = 0;
|
|
||||||
|
|
||||||
std::unique_ptr<Cache> _cache;
|
std::unique_ptr<Cache> _cache;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,10 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
|
||||||
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
|
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
|
||||||
: _timer([=] { checkNextFrameRender(); })
|
: _timer([=] { checkNextFrameRender(); })
|
||||||
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
|
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
|
||||||
|
crl::on_main_update_requests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
checkStep();
|
||||||
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiPlayer::~MultiPlayer() {
|
MultiPlayer::~MultiPlayer() {
|
||||||
|
@ -54,26 +58,22 @@ not_null<Animation*> MultiPlayer::append(
|
||||||
return _animations.back().get();
|
return _animations.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
|
void MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
|
||||||
Expects(!_active.empty());
|
Expects(!_active.empty());
|
||||||
Expects((_active.size() == 1) == (_started == kTimeUnknown));
|
Expects((_active.size() == 1) == (_started == kTimeUnknown));
|
||||||
|
|
||||||
|
const auto now = crl::now();
|
||||||
if (_active.size() == 1) {
|
if (_active.size() == 1) {
|
||||||
_started = crl::now();
|
_started = now;
|
||||||
state->start(this, _started);
|
|
||||||
return _started;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto now = crl::now();
|
|
||||||
const auto rate = state->information().frameRate;
|
const auto rate = state->information().frameRate;
|
||||||
Assert(rate != 0);
|
Assert(rate != 0);
|
||||||
|
|
||||||
const auto started = _started + _accumulatedDelay;
|
const auto started = _started + _delay;
|
||||||
const auto skipFrames = (now - started) * rate / 1000;
|
const auto skipFrames = (now - started) * rate / 1000;
|
||||||
const auto startAt = started + (1000 * skipFrames / rate);
|
|
||||||
|
|
||||||
state->start(this, startAt);
|
state->start(this, _started, _delay, skipFrames);
|
||||||
return startAt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiPlayer::start(
|
void MultiPlayer::start(
|
||||||
|
@ -81,21 +81,32 @@ void MultiPlayer::start(
|
||||||
std::unique_ptr<SharedState> state) {
|
std::unique_ptr<SharedState> state) {
|
||||||
Expects(state != nullptr);
|
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());
|
_active.emplace(animation, state.get());
|
||||||
|
|
||||||
auto information = state->information();
|
auto information = state->information();
|
||||||
startAtRightTime(state.get());
|
startAtRightTime(state.get());
|
||||||
_renderer->append(std::move(state));
|
_renderer->append(std::move(state));
|
||||||
_updates.fire({});
|
_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) {
|
void MultiPlayer::remove(not_null<Animation*> animation) {
|
||||||
|
@ -112,7 +123,7 @@ void MultiPlayer::remove(not_null<Animation*> animation) {
|
||||||
|
|
||||||
if (_active.empty()) {
|
if (_active.empty()) {
|
||||||
_started = kTimeUnknown;
|
_started = kTimeUnknown;
|
||||||
_accumulatedDelay = 0;
|
_delay = 0;
|
||||||
_nextFrameTime = kTimeUnknown;
|
_nextFrameTime = kTimeUnknown;
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
}
|
}
|
||||||
|
@ -127,7 +138,7 @@ rpl::producer<MultiUpdate> MultiPlayer::updates() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiPlayer::checkStep() {
|
void MultiPlayer::checkStep() {
|
||||||
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
||||||
return;
|
return;
|
||||||
} else if (_nextFrameTime != kTimeUnknown) {
|
} else if (_nextFrameTime != kTimeUnknown) {
|
||||||
checkNextFrameRender();
|
checkNextFrameRender();
|
||||||
|
@ -139,9 +150,6 @@ void MultiPlayer::checkStep() {
|
||||||
void MultiPlayer::checkNextFrameAvailability() {
|
void MultiPlayer::checkNextFrameAvailability() {
|
||||||
Expects(_nextFrameTime == kTimeUnknown);
|
Expects(_nextFrameTime == kTimeUnknown);
|
||||||
|
|
||||||
if (_active.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto next = kTimeUnknown;
|
auto next = kTimeUnknown;
|
||||||
for (const auto &[animation, state] : _active) {
|
for (const auto &[animation, state] : _active) {
|
||||||
const auto time = state->nextFrameDisplayTime();
|
const auto time = state->nextFrameDisplayTime();
|
||||||
|
@ -181,10 +189,10 @@ void MultiPlayer::checkNextFrameRender() {
|
||||||
} else {
|
} else {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
|
||||||
const auto exact = std::exchange(
|
markFrameDisplayed(now);
|
||||||
_nextFrameTime,
|
addTimelineDelay(now - _nextFrameTime);
|
||||||
kFrameDisplayTimeAlreadyDone);
|
|
||||||
markFrameDisplayed(now, now - exact);
|
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
||||||
_updates.fire({});
|
_updates.fire({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +206,7 @@ void MultiPlayer::updateFrameRequest(
|
||||||
_renderer->updateFrameRequest(i->second, request);
|
_renderer->updateFrameRequest(i->second, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
|
void MultiPlayer::markFrameDisplayed(crl::time now) {
|
||||||
Expects(!_active.empty());
|
Expects(!_active.empty());
|
||||||
|
|
||||||
auto displayed = 0;
|
auto displayed = 0;
|
||||||
|
@ -210,17 +218,27 @@ void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
|
||||||
continue;
|
continue;
|
||||||
} else if (now >= time) {
|
} else if (now >= time) {
|
||||||
++displayed;
|
++displayed;
|
||||||
state->markFrameDisplayed(now, delayed);
|
state->markFrameDisplayed(now);
|
||||||
} else {
|
} else {
|
||||||
++waiting;
|
++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() {
|
void MultiPlayer::markFrameShown() {
|
||||||
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
|
||||||
_nextFrameTime = kTimeUnknown;
|
_nextFrameTime = kTimeUnknown;
|
||||||
|
appendPendingToActive();
|
||||||
}
|
}
|
||||||
auto count = 0;
|
auto count = 0;
|
||||||
for (const auto &[animation, state] : _active) {
|
for (const auto &[animation, state] : _active) {
|
||||||
|
|
|
@ -56,8 +56,13 @@ public:
|
||||||
void remove(not_null<Animation*> animation);
|
void remove(not_null<Animation*> animation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
crl::time startAtRightTime(not_null<SharedState*> state);
|
void appendToActive(
|
||||||
void markFrameDisplayed(crl::time now, crl::time delayed);
|
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 checkNextFrameAvailability();
|
||||||
void checkNextFrameRender();
|
void checkNextFrameRender();
|
||||||
|
|
||||||
|
@ -65,9 +70,12 @@ private:
|
||||||
const std::shared_ptr<FrameRenderer> _renderer;
|
const std::shared_ptr<FrameRenderer> _renderer;
|
||||||
std::vector<std::unique_ptr<Animation>> _animations;
|
std::vector<std::unique_ptr<Animation>> _animations;
|
||||||
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
|
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;
|
//base::flat_map<not_null<Animation*>, not_null<SharedState*>> _paused;
|
||||||
crl::time _started = kTimeUnknown;
|
crl::time _started = kTimeUnknown;
|
||||||
crl::time _accumulatedDelay = 0;
|
crl::time _delay = 0;
|
||||||
crl::time _nextFrameTime = kTimeUnknown;
|
crl::time _nextFrameTime = kTimeUnknown;
|
||||||
rpl::event_stream<MultiUpdate> _updates;
|
rpl::event_stream<MultiUpdate> _updates;
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
|
@ -102,10 +102,10 @@ void SinglePlayer::checkNextFrameRender() {
|
||||||
} else {
|
} else {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
|
||||||
const auto exact = std::exchange(
|
const auto position = _state->markFrameDisplayed(now);
|
||||||
_nextFrameTime,
|
_state->addTimelineDelay(now - _nextFrameTime);
|
||||||
kFrameDisplayTimeAlreadyDone);
|
|
||||||
const auto position = _state->markFrameDisplayed(now, now - exact);
|
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
|
||||||
_updates.fire({ DisplayFrameRequest{ position } });
|
_updates.fire({ DisplayFrameRequest{ position } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue