Repository navigation
Budget Android pre-mounting by the display's frame, not a 60 Hz one - #59057
YevheniiKotyrlo wants to merge 4 commits into
Conversation
MountItemDispatcher stopped pre-mounting views at half of a 60 Hz frame (8.33 ms) on every display: all of a 120 Hz frame and more than a 144 Hz one. It now pre-mounts for half of the shortest interval between the frames it has run on, which is the display's frame at its fastest refresh rate, and never for longer than before. The loop reads React Native's SystemClock so MountItemDispatcherTest can drive it.
|
The refresh rate can change dynamically, so forever locking to the smallest time may be suboptimal - can we try to use the screen's current refresh rate? |
The shortest frame interval seen so far stays at the display's highest rate, so after a drop from 120 Hz back to 60 Hz pre-mounting keeps a 120 Hz budget, half of what the frame allows. DisplayFrameInterval reads the default display's refresh rate from DisplayManager when the display reports a change, never per frame, and MountItemDispatcher pre-mounts for half of that frame.
|
Updated in 35f8355: pre-mounting now takes half of the default display's current frame, read from |
Half of the display's frame grows below 60 Hz, to 500 ms at the 1 Hz an adaptive panel can idle at, so the budget is now the shorter of half the display's frame and half of a 60 Hz frame. A display that reports no refresh rate gets half of a 60 Hz frame rather than an assumed rate.
|
Follow-up in 3d780fb: below 60 Hz the budget stays at today's 8.33 ms instead of half of the longer frame, since an adaptive panel idling at 1 Hz would otherwise pre-mount for 500 ms, and a display that reports no refresh rate gets 8.33 ms as well. |
While 500ms is extreme, I do think it's reasonable for us to use more than 8ms here when the system has reduced frame rate (eg in low power mode?). Maybe make the max the equivalent of 30hz / 16ms? |
A display the system slows below 60 Hz can spend more of each frame pre-mounting, so the ceiling is now half of a 30 Hz frame (16.67 ms) rather than half of a 60 Hz one: from 30 Hz up the budget is half of the display's frame, and below 30 Hz it is 16.67 ms. A display that reports no refresh rate is still budgeted as a 60 Hz one.
|
9e2fa9c caps the budget at half of a 30 Hz frame (16.67 ms) instead of 8.33 ms; a display that reports no refresh rate keeps 8.33 ms. |
Summary:
MountItemDispatcher.dispatchPreMountItemsstops pre-mounting atframeTimeNanos + FRAME_TIME_NS / 2, whereFRAME_TIME_NSis a 60 Hz frame. That budget is 8.33 ms on every display: all of a 120 Hz frame and more than a 144 Hz one, so a frame that pre-mounts a large backlog on a fast display spends its whole budget before it traverses.The dispatcher now pre-mounts for half of the default display's current frame, and never longer than half of a 30 Hz frame: a display running below 30 Hz, such as an adaptive panel idling at 1 Hz, gets 16.67 ms instead of half of its long frame.
DisplayFrameIntervalreads the refresh rate fromDisplayManagerwhenFabricUIManagerinitializes and again whenever the display reports a change, so a frame reads a cached interval rather than the display; a display that reports no refresh rate keeps today's 8.33 ms. The loop readscom.facebook.react.common.SystemClockso a test can drive it.Changelog:
[ANDROID] [FIXED] - Budget view pre-mounting by the display's current refresh rate instead of a 60 Hz frame
Test Plan:
MountItemDispatcherTest(new) covers the budget at 30, 48, 60, 90, 120, 144, 165, 185 and 240 Hz, at 1, 10 and 24 Hz, while the display reports no refresh rate, after the display drops from 120 Hz back to 60 Hz, and across a skipped frame.DisplayFrameIntervalTest(new) covers reading the default display's frame from 1 Hz to 240 Hz, following a refresh-rate change the display reports, keeping the last frame once stopped, and reporting no frame while the display reports no refresh rate.Every queued item takes 1 ms. With the 60 Hz budget, the rate cases fail:
expected:<{30=[17, 48=11, 60=9, 90=6, 120=5, 144=4, 165=4, 185=3, 240=3]}> but was:<{30=[9, 48=9, 60=9, 90=9, 120=9, 144=9, 165=9, 185=9, 240=9]}>andexpected:<{1=[17, 10=17, 24=17]}> but was:<{1=[9, 10=9, 24=9]}>. Without the 16.67 ms ceiling, the cases below 30 Hz fail:expected:<{1=[17, 10=17, 24=17]}> but was:<{1=[20, 10=20, 24=20]}>../gradlew :packages:react-native:ReactAndroid:testDebugUnitTest -PenableWarningsAsErrors=true: 730 tests, 0 failures./gradlew :packages:react-native:ReactAndroid:compileReleaseKotlin :packages:react-native:ReactAndroid:compileReleaseJavaWithJavac: cleannode ./scripts/format-kotlin.js --checkandnode ./scripts/format-java.js --check: cleanI didn't run the full Android
build, the template and HelloWorld app builds, or the Android e2e jobs locally.