Changeset 286671 in webkit
- Timestamp:
- Dec 8, 2021, 11:58:13 AM (5 years ago)
- Location:
- trunk/Source/WebKit
- Files:
-
- 3 edited
-
ChangeLog (modified) (1 diff)
-
WebProcess/WebPage/MomentumEventDispatcher.cpp (modified) (10 diffs)
-
WebProcess/WebPage/MomentumEventDispatcher.h (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebKit/ChangeLog
r286661 r286671 1 2021-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 1 34 2021-12-08 Chris Dumez <cdumez@apple.com> 2 35 -
trunk/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.cpp
r286566 r286671 114 114 115 115 auto combinedPhase = (event.phase() << 8) | (event.momentumPhase()); 116 m_currentLogState.latestEventPhase = combinedPhase;117 116 m_currentLogState.totalEventOffset += event.delta().height(); 118 117 if (!isMomentumEventDuringSyntheticGesture) { 119 118 // Log events that we don't block to the generated offsets log as well, 120 119 // even though we didn't technically generate them, just passed them through. 121 m_currentLogState.latestGeneratedPhase = combinedPhase;122 120 m_currentLogState.totalGeneratedOffset += event.delta().height(); 123 } 124 pushLogEntry(); 121 pushLogEntry(combinedPhase, combinedPhase); 122 } else 123 pushLogEntry(0, combinedPhase); 124 125 125 #endif 126 126 … … 178 178 179 179 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING) 180 m_currentLogState.latestGeneratedPhase = phase;181 180 m_currentLogState.totalGeneratedOffset += appKitAcceleratedDelta.height(); 182 pushLogEntry( );181 pushLogEntry(phase, 0); 183 182 #endif 184 183 } … … 220 219 221 220 #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()); 223 222 m_dispatcher.queue().dispatchAfter(1_s, [this] { 224 223 flushLog(); … … 295 294 WebCore::FloatSize MomentumEventDispatcher::consumeDeltaForCurrentTime() 296 295 { 296 WebCore::FloatSize delta; 297 297 298 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 } 306 308 307 309 m_currentGesture.currentOffset += delta; … … 364 366 void MomentumEventDispatcher::buildOffsetTableWithInitialDelta(WebCore::FloatSize initialUnacceleratedDelta) 365 367 { 366 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING)367 m_currentGesture.carryOffset = { };368 #endif369 368 m_currentGesture.offsetTable.clear(); 370 369 371 370 WebCore::FloatSize accumulatedOffset; 372 371 WebCore::FloatSize unacceleratedDelta = initialUnacceleratedDelta; 372 373 float physicalCurveMultiplier = idealCurveFrameRate / m_currentGesture.accelerationCurve->frameRate(); 374 bool inTail = false; 375 WebCore::FloatSize tailCarry; 373 376 374 377 do { … … 376 379 std::tie(unacceleratedDelta, acceleratedDelta) = computeNextDelta(unacceleratedDelta); 377 380 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 378 395 accumulatedOffset += acceleratedDelta; 379 396 m_currentGesture.offsetTable.append(accumulatedOffset); 397 380 398 } while (std::abs(unacceleratedDelta.width()) > 0.5 || std::abs(unacceleratedDelta.height()) > 0.5); 381 399 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 407 void 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); 385 486 } 386 487 … … 434 535 unacceleratedDelta.scale(decayRate); 435 536 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 #endif455 456 537 // The delta queue operates on pre-acceleration deltas, so insert the new event *before* accelerating. 457 didReceiveScrollEventWithInterval( quantizedUnacceleratedDelta, idealCurveFrameInterval);538 didReceiveScrollEventWithInterval(unacceleratedDelta, idealCurveFrameInterval); 458 539 459 540 auto accelerateAxis = [&] (HistoricalDeltas& deltas, float value) { … … 500 581 501 582 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()) 504 585 ); 505 586 … … 513 594 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING) 514 595 515 void MomentumEventDispatcher::pushLogEntry( )596 void MomentumEventDispatcher::pushLogEntry(uint32_t generatedPhase, uint32_t eventPhase) 516 597 { 517 598 m_currentLogState.time = MonotonicTime::now(); 599 m_currentLogState.generatedPhase = generatedPhase; 600 m_currentLogState.eventPhase = eventPhase; 518 601 m_log.append(m_currentLogState); 519 602 } … … 530 613 RELEASE_LOG(ScrollAnimations, "MomentumEventDispatcher event log: time,generatedOffset,generatedPhase,eventOffset,eventPhase"); 531 614 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); 533 616 534 617 m_log.clear(); -
trunk/Source/WebKit/WebProcess/WebPage/MomentumEventDispatcher.h
r286512 r286671 28 28 #if ENABLE(MOMENTUM_EVENT_DISPATCHER) 29 29 30 // FIXME: Remove this once we decide which version we want.31 #define ENABLE_MOMENTUM_EVENT_DISPATCHER_PREMATURE_ROUNDING 032 30 #define ENABLE_MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING 1 33 31 … … 81 79 82 80 void buildOffsetTableWithInitialDelta(WebCore::FloatSize); 81 void equalizeTailGaps(); 83 82 84 83 // Once consumed, this delta *must* be dispatched in an event. … … 92 91 93 92 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING) 94 void pushLogEntry( );93 void pushLogEntry(uint32_t generatedPhase, uint32_t eventPhase); 95 94 void flushLog(); 96 95 … … 103 102 float totalEventOffset { 0 }; 104 103 105 uint32_t latestGeneratedPhase { 0 };106 uint32_t latestEventPhase { 0 };104 uint32_t generatedPhase { 0 }; 105 uint32_t eventPhase { 0 }; 107 106 }; 108 107 LogEntry m_currentLogState; … … 134 133 MonotonicTime startTime; 135 134 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 }; 137 139 138 140 #if ENABLE(MOMENTUM_EVENT_DISPATCHER_TEMPORARY_LOGGING)
Note:
See TracChangeset
for help on using the changeset viewer.