⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 286671 in webkit


Ignore:
Timestamp:
Dec 8, 2021, 11:58:13 AM (5 years ago)
Author:
timothy_horton@apple.com
Message:

Momentum Event Dispatcher: Momentum tail should have montonically decreasing deltas and tail gaps
https://bugs.webkit.org/show_bug.cgi?id=233993
<rdar://problem/86118367>

Reviewed by Simon Fraser.

  • WebProcess/WebPage/MomentumEventDispatcher.cpp:

(WebKit::MomentumEventDispatcher::consumeDeltaForCurrentTime):
(WebKit::MomentumEventDispatcher::buildOffsetTableWithInitialDelta):
(WebKit::MomentumEventDispatcher::computeNextDelta):
In order to avoid visual stutter due to integral rounding of the
deltas we dispatch in the momentum phase, switch to a table of
deltas instead of interpolating along the offset curve during the
tail end of the animation (when the rounding makes up a large
proportion of each delta).

(WebKit::MomentumEventDispatcher::equalizeTailGaps):
Sort the deltas up until the first zero to ensure a lack of unexpected
perceptual acceleration, and then inject skipped frames in order to
ensure that frames that have effective scroll movement (non-zero deltas)
are always spaced equally-or-further apart than earlier ones, but
never closer together (which, again, would be percieved as acceleration).

(WebKit::MomentumEventDispatcher::handleWheelEvent):
(WebKit::MomentumEventDispatcher::pushLogEntry):
(WebKit::MomentumEventDispatcher::flushLog):
Lastly, adjust some logging to make it easier to tell which row in
the output corresponds to an event delta or generated delta, so it's
easier to find skipped frames.

  • WebProcess/WebPage/MomentumEventDispatcher.h:
Location:
trunk/Source/WebKit
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r286661 r286671  
     12021-12-08  Tim Horton  <timothy_horton@apple.com>
     2
     3        Momentum Event Dispatcher: Momentum tail should have montonically decreasing deltas and tail gaps
     4        https://bugs.webkit.org/show_bug.cgi?id=233993
     5        <rdar://problem/86118367>
     6
     7        Reviewed by Simon Fraser.
     8
     9        * WebProcess/WebPage/MomentumEventDispatcher.cpp:
     10        (WebKit::MomentumEventDispatcher::consumeDeltaForCurrentTime):
     11        (WebKit::MomentumEventDispatcher::buildOffsetTableWithInitialDelta):
     12        (WebKit::MomentumEventDispatcher::computeNextDelta):
     13        In order to avoid visual stutter due to integral rounding of the
     14        deltas we dispatch in the momentum phase, switch to a table of
     15        deltas instead of interpolating along the offset curve during the
     16        tail end of the animation (when the rounding makes up a large
     17        proportion of each delta).
     18
     19        (WebKit::MomentumEventDispatcher::equalizeTailGaps):
     20        Sort the deltas up until the first zero to ensure a lack of unexpected
     21        perceptual acceleration, and then inject skipped frames in order to
     22        ensure that frames that have effective scroll movement (non-zero deltas)
     23        are always spaced equally-or-further apart than earlier ones, but
     24        never closer together (which, again, would be percieved as acceleration).
     25
     26        (WebKit::MomentumEventDispatcher::handleWheelEvent):
     27        (WebKit::MomentumEventDispatcher::pushLogEntry):
     28        (WebKit::MomentumEventDispatcher::flushLog):
     29        Lastly, adjust some logging to make it easier to tell which row in
     30        the output corresponds to an event delta or generated delta, so it's
     31        easier to find skipped frames.
     32        * WebProcess/WebPage/MomentumEventDispatcher.h:
     33
    1342021-12-08  Chris Dumez  <cdumez@apple.com>
    235
  • trunk/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp

    r286566 r286671  
    114114
    115115    auto combinedPhase = (event.phase() << 8) | (event.momentumPhase());
    116     m_currentLogState.latestEventPhase = combinedPhase;
    117116    m_currentLogState.totalEventOffset += event.delta().height();
    118117    if (!isMomentumEventDuringSyntheticGesture) {
    119118        // Log events that we don't block to the generated offsets log as well,
    120119        // even though we didn't technically generate them, just passed them through.
    121         m_currentLogState.latestGeneratedPhase = combinedPhase;
    122120        m_currentLogState.totalGeneratedOffset += event.delta().height();
    123     }
    124     pushLogEntry();
     121        pushLogEntry(combinedPhase, combinedPhase);
     122    } else
     123        pushLogEntry(0, combinedPhase);
     124   
    125125#endif
    126126
     
    178178
    179179#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
    180     m_currentLogState.latestGeneratedPhase = phase;
    181180    m_currentLogState.totalGeneratedOffset += appKitAcceleratedDelta.height();
    182     pushLogEntry();
     181    pushLogEntry(phase, 0);
    183182#endif
    184183}
     
    220219
    221220#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
    222     RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher saw momentum end phase with total offset %.1f %.1f, duration %f (event offset would have been %.1f %.1f)", m_currentGesture.currentOffset.width(), m_currentGesture.currentOffset.height(), (MonotonicTime::now() - m_currentGesture.startTime).seconds(), m_currentGesture.accumulatedEventOffset.width(), m_currentGesture.accumulatedEventOffset.height());
     221    RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher saw momentum end phase with total offset %.1f %.1f, duration %f (event offset would have been %.1f %.1f) (tail index %d of %zu)", m_currentGesture.currentOffset.width(), m_currentGesture.currentOffset.height(), (MonotonicTime::now() - m_currentGesture.startTime).seconds(), m_currentGesture.accumulatedEventOffset.width(), m_currentGesture.accumulatedEventOffset.height(), m_currentGesture.currentTailDeltaIndex, m_currentGesture.tailDeltaTable.size());
    223222    m_dispatcher.queue().dispatchAfter(1_s, [this] {
    224223        flushLog();
     
    295294WebCore::FloatSize MomentumEventDispatcher::consumeDeltaForCurrentTime()
    296295{
     296    WebCore::FloatSize delta;
     297
    297298    auto animationTime = MonotonicTime::now() - m_currentGesture.startTime;
    298     auto desiredOffset = offsetAtTime(animationTime);
    299 
    300 #if !ENABLE(MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING)
    301     // Intentional delta rounding (but at the end!).
    302     WebCore::FloatSize delta = roundedIntSize(desiredOffset - m_currentGesture.currentOffset);
    303 #else
    304     WebCore::FloatSize delta = desiredOffset - m_currentGesture.currentOffset;
    305 #endif
     299    if (animationTime < m_currentGesture.tailStartDelay) {
     300        auto desiredOffset = offsetAtTime(animationTime);
     301        delta = roundedIntSize(desiredOffset - m_currentGesture.currentOffset);
     302    } else {
     303        if (m_currentGesture.currentTailDeltaIndex < m_currentGesture.tailDeltaTable.size())
     304            delta = -m_currentGesture.tailDeltaTable[m_currentGesture.currentTailDeltaIndex++];
     305        else
     306            delta = { };
     307    }
    306308
    307309    m_currentGesture.currentOffset += delta;
     
    364366void MomentumEventDispatcher::buildOffsetTableWithInitialDelta(WebCore::FloatSize initialUnacceleratedDelta)
    365367{
    366 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING)
    367     m_currentGesture.carryOffset = { };
    368 #endif
    369368    m_currentGesture.offsetTable.clear();
    370369
    371370    WebCore::FloatSize accumulatedOffset;
    372371    WebCore::FloatSize unacceleratedDelta = initialUnacceleratedDelta;
     372
     373    float physicalCurveMultiplier = idealCurveFrameRate / m_currentGesture.accelerationCurve->frameRate();
     374    bool inTail = false;
     375    WebCore::FloatSize tailCarry;
    373376
    374377    do {
     
    376379        std::tie(unacceleratedDelta, acceleratedDelta) = computeNextDelta(unacceleratedDelta);
    377380
     381        const float tailStartUnacceleratedDelta = 6.f;
     382        if (!inTail && std::abs(unacceleratedDelta.width()) < tailStartUnacceleratedDelta && std::abs(unacceleratedDelta.height()) < tailStartUnacceleratedDelta) {
     383            inTail = true;
     384            m_currentGesture.tailStartDelay = idealCurveFrameInterval * m_currentGesture.offsetTable.size();
     385        }
     386
     387        if (inTail) {
     388            auto tailDelta = acceleratedDelta * physicalCurveMultiplier;
     389            auto deltaWithCarry = tailDelta + tailCarry;
     390            auto quantizedDelta = roundedIntSize(deltaWithCarry);
     391            tailCarry = deltaWithCarry - quantizedDelta;
     392            m_currentGesture.tailDeltaTable.append(quantizedDelta);
     393        }
     394
    378395        accumulatedOffset += acceleratedDelta;
    379396        m_currentGesture.offsetTable.append(accumulatedOffset);
     397
    380398    } while (std::abs(unacceleratedDelta.width()) > 0.5 || std::abs(unacceleratedDelta.height()) > 0.5);
    381399
    382 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
    383     RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher built table with %ld frames, initial delta %f %f, distance %f %f (initial delta from last changed event %f %f)", m_currentGesture.offsetTable.size(), initialUnacceleratedDelta.width(), initialUnacceleratedDelta.height(), accumulatedOffset.width(), accumulatedOffset.height(), m_lastActivePhaseDelta.width(), m_lastActivePhaseDelta.height());
    384 #endif
     400    equalizeTailGaps();
     401
     402#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
     403    RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher built table with %ld frames, %.1f seconds (%ld tail frames, %.1f seconds, starting at %.1f), initial delta %.1f %.1f, distance %.1f %.1f (initial delta from last changed event %.1f %.1f)", m_currentGesture.offsetTable.size(), idealCurveFrameInterval.seconds() * m_currentGesture.offsetTable.size(), m_currentGesture.tailDeltaTable.size(), m_currentGesture.tailDeltaTable.size() * (1.f / m_currentGesture.accelerationCurve->frameRate()), m_currentGesture.tailStartDelay.seconds(), initialUnacceleratedDelta.width(), initialUnacceleratedDelta.height(), accumulatedOffset.width(), accumulatedOffset.height(), m_lastActivePhaseDelta.width(), m_lastActivePhaseDelta.height());
     404#endif
     405}
     406
     407void MomentumEventDispatcher::equalizeTailGaps()
     408{
     409    // Sort the deltas up until the first zero to ensure a lack of unexpected
     410    // perceptual acceleration, and then inject skipped frames in order to
     411    // ensure that frames that have effective scroll movement (non-zero deltas)
     412    // are always spaced equally-or-further apart than earlier ones, but
     413    // never closer together.
     414
     415    auto& table = m_currentGesture.tailDeltaTable;
     416    size_t initialTableSize = table.size();
     417
     418    enum Axis { Horizontal, Vertical };
     419    Vector<float> deltas[2];
     420    unsigned firstZeroIndex[2] = { 0, 0 };
     421    deltas[Horizontal].reserveInitialCapacity(initialTableSize);
     422    deltas[Vertical].reserveInitialCapacity(initialTableSize);
     423    for (unsigned i = 0; i < initialTableSize; i++) {
     424        deltas[Horizontal].uncheckedAppend(table[i].width());
     425        if (!firstZeroIndex[Horizontal] && !table[i].width())
     426            firstZeroIndex[Horizontal] = i;
     427
     428        deltas[Vertical].uncheckedAppend(table[i].height());
     429        if (!firstZeroIndex[Vertical] && !table[i].height())
     430            firstZeroIndex[Vertical] = i;
     431    }
     432   
     433    if (auto index = firstZeroIndex[Horizontal])
     434        std::sort(deltas[Horizontal].begin(), std::next(deltas[Horizontal].begin(), index));
     435    if (auto index = firstZeroIndex[Vertical])
     436        std::sort(deltas[Vertical].begin(), std::next(deltas[Vertical].begin(), index));
     437
     438    // GapSize is a count of contiguous frames with zero deltas.
     439    typedef unsigned GapSize[2];
     440    GapSize minimumGap = { 0, 0 };
     441    GapSize currentGap = { 0, 0 };
     442    GapSize remainingGapToGenerate = { 0, 0 };
     443    unsigned originalTableIndex[2] = { 0, 0 };
     444
     445    auto takeNextDelta = [&] (uint8_t axis) -> float {
     446        if (originalTableIndex[axis] >= initialTableSize)
     447            return 0.f;
     448
     449        if (remainingGapToGenerate[axis]) {
     450            --remainingGapToGenerate[axis];
     451            ++currentGap[axis];
     452            return 0.f;
     453        }
     454
     455        auto value = deltas[axis][originalTableIndex[axis]];
     456        if (value) {
     457            minimumGap[axis] = std::max(minimumGap[axis], currentGap[axis]);
     458            remainingGapToGenerate[axis] = minimumGap[axis] - currentGap[axis];
     459            if (remainingGapToGenerate[axis]) {
     460                --remainingGapToGenerate[axis];
     461                ++currentGap[axis];
     462                return 0.f;
     463            }
     464
     465            currentGap[axis] = 0;
     466        } else
     467            ++currentGap[axis];
     468
     469        ++originalTableIndex[axis];
     470
     471        return value;
     472    };
     473
     474    size_t finalTableSize = 0;
     475    table.shrink(0);
     476
     477    while (originalTableIndex[Horizontal] < initialTableSize || originalTableIndex[Vertical] < initialTableSize) {
     478        WebCore::FloatSize delta(takeNextDelta(Horizontal), takeNextDelta(Vertical));
     479        table.append(delta);
     480
     481        if (!delta.isZero())
     482            finalTableSize = table.size();
     483    }
     484
     485    table.shrink(finalTableSize);
    385486}
    386487
     
    434535    unacceleratedDelta.scale(decayRate);
    435536
    436     auto quantizedUnacceleratedDelta = unacceleratedDelta;
    437 
    438 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING)
    439     // Round and carry.
    440     int32_t quantizedX = std::round(quantizedUnacceleratedDelta.width());
    441     int32_t quantizedY = std::round(quantizedUnacceleratedDelta.height());
    442 
    443     if (std::abs(quantizedUnacceleratedDelta.width()) < 1 && std::abs(quantizedUnacceleratedDelta.height()) < 1) {
    444         float deltaXIncludingCarry = quantizedUnacceleratedDelta.width() + m_currentGesture.carryOffset.width();
    445         float deltaYIncludingCarry = quantizedUnacceleratedDelta.height() + m_currentGesture.carryOffset.height();
    446 
    447         // Intentional truncation.
    448         quantizedX = deltaXIncludingCarry;
    449         quantizedY = deltaYIncludingCarry;
    450         m_currentGesture.carryOffset = { deltaXIncludingCarry - quantizedX, deltaYIncludingCarry - quantizedY };
    451     }
    452 
    453     quantizedUnacceleratedDelta = { static_cast<float>(quantizedX), static_cast<float>(quantizedY) };
    454 #endif
    455 
    456537    // The delta queue operates on pre-acceleration deltas, so insert the new event *before* accelerating.
    457     didReceiveScrollEventWithInterval(quantizedUnacceleratedDelta, idealCurveFrameInterval);
     538    didReceiveScrollEventWithInterval(unacceleratedDelta, idealCurveFrameInterval);
    458539
    459540    auto accelerateAxis = [&] (HistoricalDeltas& deltas, float value) {
     
    500581
    501582    WebCore::FloatSize acceleratedDelta(
    502         accelerateAxis(m_deltaHistoryX, quantizedUnacceleratedDelta.width()),
    503         accelerateAxis(m_deltaHistoryY, quantizedUnacceleratedDelta.height())
     583        accelerateAxis(m_deltaHistoryX, unacceleratedDelta.width()),
     584        accelerateAxis(m_deltaHistoryY, unacceleratedDelta.height())
    504585    );
    505586
     
    513594#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
    514595
    515 void MomentumEventDispatcher::pushLogEntry()
     596void MomentumEventDispatcher::pushLogEntry(uint32_t generatedPhase, uint32_t eventPhase)
    516597{
    517598    m_currentLogState.time = MonotonicTime::now();
     599    m_currentLogState.generatedPhase = generatedPhase;
     600    m_currentLogState.eventPhase = eventPhase;
    518601    m_log.append(m_currentLogState);
    519602}
     
    530613    RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher event log: time,generatedOffset,generatedPhase,eventOffset,eventPhase");
    531614    for (const auto& entry : m_log)
    532         RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher event log: %f,%f,%d,%f,%d", (entry.time - startTime).seconds(), entry.totalGeneratedOffset, entry.latestGeneratedPhase, entry.totalEventOffset, entry.latestEventPhase);
     615        RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher event log: %f,%f,%d,%f,%d", (entry.time - startTime).seconds(), entry.totalGeneratedOffset, entry.generatedPhase, entry.totalEventOffset, entry.eventPhase);
    533616
    534617    m_log.clear();
  • trunk/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h

    r286512 r286671  
    2828#if ENABLE(MOMENTUM_EVENT_DISPATCHER)
    2929
    30 // FIXME: Remove this once we decide which version we want.
    31 #define ENABLE_MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING 0
    3230#define ENABLE_MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING 1
    3331
     
    8179
    8280    void buildOffsetTableWithInitialDelta(WebCore::FloatSize);
     81    void equalizeTailGaps();
    8382
    8483    // Once consumed, this delta *must* be dispatched in an event.
     
    9291
    9392#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
    94     void pushLogEntry();
     93    void pushLogEntry(uint32_t generatedPhase, uint32_t eventPhase);
    9594    void flushLog();
    9695
     
    103102        float totalEventOffset { 0 };
    104103
    105         uint32_t latestGeneratedPhase { 0 };
    106         uint32_t latestEventPhase { 0 };
     104        uint32_t generatedPhase { 0 };
     105        uint32_t eventPhase { 0 };
    107106    };
    108107    LogEntry m_currentLogState;
     
    134133        MonotonicTime startTime;
    135134
    136         Vector<WebCore::FloatSize> offsetTable;
     135        Vector<WebCore::FloatSize> offsetTable; // Always at 60Hz intervals.
     136        Vector<WebCore::FloatSize> tailDeltaTable; // Always at event dispatch intervals.
     137        Seconds tailStartDelay;
     138        unsigned currentTailDeltaIndex { 0 };
    137139
    138140#if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
Note: See TracChangeset for help on using the changeset viewer.