Animation fps improvements

This commit is contained in:
Ali 2022-12-25 17:50:32 +04:00
parent 7869790318
commit 812ad9b030
7 changed files with 167 additions and 123 deletions

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors", "-warnings-as-errors",
], ],
deps = [ deps = [
"//submodules/Display"
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
import UIKit import UIKit
import Display
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float @_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
@ -15,105 +16,31 @@ private extension UIView {
} }
} }
@objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate {
private let keyPath: String?
var completion: ((Bool) -> Void)?
init(animation: CAAnimation, completion: ((Bool) -> Void)?) {
if let animation = animation as? CABasicAnimation {
self.keyPath = animation.keyPath
} else {
self.keyPath = nil
}
self.completion = completion
super.init()
}
@objc func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if let anim = anim as? CABasicAnimation {
if anim.keyPath != self.keyPath {
return
}
}
if let completion = self.completion {
completion(flag)
}
}
}
private func makeSpringAnimation(keyPath: String) -> CASpringAnimation {
let springAnimation = CASpringAnimation(keyPath: keyPath)
springAnimation.mass = 3.0;
springAnimation.stiffness = 1000.0
springAnimation.damping = 500.0
springAnimation.duration = 0.5
springAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
return springAnimation
}
private extension CALayer { private extension CALayer {
func makeAnimation(from: AnyObject?, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) -> CAAnimation { func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) {
let timingFunction: String
let mediaTimingFunction: CAMediaTimingFunction?
switch curve { switch curve {
case .spring: case .spring:
let animation = makeSpringAnimation(keyPath: keyPath) timingFunction = kCAMediaTimingFunctionSpring
animation.fromValue = from mediaTimingFunction = nil
animation.toValue = to
animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = .forwards
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
}
let k = Float(UIView.animationDurationFactor)
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
animation.speed = speed * Float(animation.duration / duration)
animation.isAdditive = additive
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor
animation.fillMode = .both
}
return animation
default: default:
let k = Float(UIView.animationDurationFactor) timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
var speed: Float = 1.0 mediaTimingFunction = curve.asTimingFunction()
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
let animation = CABasicAnimation(keyPath: keyPath)
if let from = from {
animation.fromValue = from
}
animation.toValue = to
animation.duration = duration
animation.timingFunction = curve.asTimingFunction()
animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = .both
animation.speed = speed
animation.isAdditive = additive
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
}
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor
animation.fillMode = .both
}
return animation
} }
}
func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) { self.animate(
let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, duration: duration, delay: delay, curve: curve, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) from: from,
self.add(animation, forKey: additive ? nil : keyPath) to: to,
keyPath: keyPath,
timingFunction: timingFunction,
duration: duration,
delay: delay,
mediaTimingFunction: mediaTimingFunction,
removeOnCompletion: removeOnCompletion,
additive: additive,
completion: completion
)
} }
} }

View File

@ -62,6 +62,14 @@ private final class FrameRangeContext {
if self.animationCount != 0 { if self.animationCount != 0 {
if self.displayLink == nil { if self.displayLink == nil {
let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent)) let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent))
if #available(iOS 15.0, *) {
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
if maxFps > 61.0 {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
}
}
self.displayLink = displayLink self.displayLink = displayLink
displayLink.add(to: .main, forMode: .common) displayLink.add(to: .main, forMode: .common)
displayLink.isPaused = false displayLink.isPaused = false
@ -95,9 +103,18 @@ public extension CAAnimation {
private func adjustFrameRate(animation: CAAnimation) { private func adjustFrameRate(animation: CAAnimation) {
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
if let animation = animation as? CABasicAnimation {
if animation.keyPath == "opacity" {
return
}
}
let maxFps = Float(UIScreen.main.maximumFramesPerSecond) let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
if maxFps > 61.0 { if maxFps > 61.0 {
animation.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) #if DEBUG
//let _ = frameRangeContext.add()
#endif
animation.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: maxFps, preferred: maxFps)
} }
} }
} }
@ -138,32 +155,64 @@ public extension CALayer {
return animation return animation
} else if timingFunction == kCAMediaTimingFunctionSpring { } else if timingFunction == kCAMediaTimingFunctionSpring {
let animation = makeSpringAnimation(keyPath) if duration == 0.5 {
animation.fromValue = from let animation = makeSpringAnimation(keyPath)
animation.toValue = to animation.fromValue = from
animation.isRemovedOnCompletion = removeOnCompletion animation.toValue = to
animation.fillMode = .forwards animation.isRemovedOnCompletion = removeOnCompletion
if let completion = completion { animation.fillMode = .forwards
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) if let completion = completion {
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
}
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
animation.speed = speed * Float(animation.duration / duration)
animation.isAdditive = additive
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
animation.fillMode = .both
}
adjustFrameRate(animation: animation)
return animation
} else {
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
let animation = CABasicAnimation(keyPath: keyPath)
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.380, 0.700, 0.125, 1.000)
animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = .forwards
animation.speed = speed
animation.isAdditive = additive
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
}
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
animation.fillMode = .both
}
adjustFrameRate(animation: animation)
return animation
} }
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
animation.speed = speed * Float(animation.duration / duration)
animation.isAdditive = additive
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
animation.fillMode = .both
}
adjustFrameRate(animation: animation)
return animation
} else { } else {
let k = Float(UIView.animationDurationFactor()) let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0 var speed: Float = 1.0
@ -178,7 +227,13 @@ public extension CALayer {
if let mediaTimingFunction = mediaTimingFunction { if let mediaTimingFunction = mediaTimingFunction {
animation.timingFunction = mediaTimingFunction animation.timingFunction = mediaTimingFunction
} else { } else {
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction)) switch timingFunction {
case CAMediaTimingFunctionName.linear.rawValue, CAMediaTimingFunctionName.easeIn.rawValue, CAMediaTimingFunctionName.easeOut.rawValue, CAMediaTimingFunctionName.easeInEaseOut.rawValue, CAMediaTimingFunctionName.default.rawValue:
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction))
default:
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
}
} }
animation.isRemovedOnCompletion = removeOnCompletion animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = .forwards animation.fillMode = .forwards

View File

@ -161,7 +161,7 @@ public final class PeerOnlineMarkerNode: ASDisplayNode {
} }
if animated { if animated {
let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.0 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0) let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.001 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
let targetScale: CGFloat = online ? 1.0 : 0.0 let targetScale: CGFloat = online ? 1.0 : 0.0
if initialScale != targetScale { if initialScale != targetScale {
strongSelf.iconNode.isHidden = false strongSelf.iconNode.isHidden = false

View File

@ -209,8 +209,64 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
) )
} }
private final class AnimationSupportContext {
private let window: UIWindow
private let testView: UIView
private var animationCount: Int = 0
private var displayLink: CADisplayLink?
init(window: UIWindow) {
self.window = window
self.testView = UIView()
window.addSubview(self.testView)
self.testView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0))
self.testView.backgroundColor = .black
}
func add() {
self.animationCount += 1
self.update()
}
func remove() {
self.animationCount -= 1
if self.animationCount < 0 {
self.animationCount = 0
assertionFailure()
}
self.update()
}
@objc func displayEvent() {
self.testView.frame = CGRect(origin: CGPoint(x: self.testView.frame.minX == 0.0 ? 1.0 : 0.0, y: 0.0), size: self.testView.bounds.size)
}
private func update() {
if self.animationCount != 0 {
if self.displayLink == nil {
let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent))
if #available(iOS 15.0, *) {
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
if maxFps > 61.0 {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
}
}
self.displayLink = displayLink
displayLink.add(to: .main, forMode: .common)
displayLink.isPaused = false
}
} else if let displayLink = self.displayLink {
self.displayLink = nil
displayLink.invalidate()
}
}
}
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate { @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate {
@objc var window: UIWindow? @objc var window: UIWindow?
private var animationSupportContext: AnimationSupportContext?
var nativeWindow: (UIWindow & WindowHost)? var nativeWindow: (UIWindow & WindowHost)?
var mainWindow: Window1! var mainWindow: Window1!
private var dataImportSplash: LegacyDataImportSplash? private var dataImportSplash: LegacyDataImportSplash?
@ -306,6 +362,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.window = window self.window = window
self.nativeWindow = window self.nativeWindow = window
//self.animationSupportContext = AnimationSupportContext(window: window)
//self.animationSupportContext?.add()
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in

View File

@ -55,6 +55,7 @@ CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath)
springAnimation.damping = 500.0f; springAnimation.damping = 500.0f;
springAnimation.duration = 0.5; springAnimation.duration = 0.5;
springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
return springAnimation; return springAnimation;
} }

View File

@ -179,7 +179,8 @@ static bool notyfyingShiftState = false;
[RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)];
if (@available(iOS 15.0, *)) { if (@available(iOS 16.0, *)) {
} else if (@available(iOS 15.0, *)) {
[RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)];
} }
}); });