mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
361 lines
16 KiB
Swift
361 lines
16 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import LottieMetal
|
|
import LottieCpp
|
|
import RLottieBinding
|
|
import MetalEngine
|
|
import Display
|
|
import LottieSwift
|
|
import SoftwareLottieRenderer
|
|
|
|
@available(iOS 13.0, *)
|
|
private final class ReferenceCompareTest {
|
|
private let view: UIView
|
|
private let imageView = UIImageView()
|
|
private let referenceImageView = UIImageView()
|
|
private let deltaImageView = UIImageView()
|
|
|
|
init(view: UIView, testNonReference: Bool) {
|
|
lottieSwift_getPathNativeBoundingBox = { path in
|
|
return getPathNativeBoundingBox(path)
|
|
}
|
|
|
|
self.view = view
|
|
|
|
self.view.backgroundColor = .white
|
|
|
|
let topInset: CGFloat = 50.0
|
|
|
|
self.view.addSubview(self.imageView)
|
|
self.imageView.layer.magnificationFilter = .nearest
|
|
self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0))
|
|
self.imageView.backgroundColor = self.view.backgroundColor
|
|
self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
|
|
|
|
self.view.addSubview(self.referenceImageView)
|
|
self.referenceImageView.layer.magnificationFilter = .nearest
|
|
self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
|
|
self.referenceImageView.backgroundColor = self.view.backgroundColor
|
|
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
|
|
|
|
self.view.addSubview(self.deltaImageView)
|
|
self.deltaImageView.layer.magnificationFilter = .nearest
|
|
self.deltaImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0 + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
|
|
self.deltaImageView.backgroundColor = self.view.backgroundColor
|
|
self.deltaImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
|
|
|
|
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
|
|
|
|
Task.detached {
|
|
let sizeMapping: [String: Int] = [
|
|
"5170488605398795246.json": 512,
|
|
"35707580709863506.json": 512,
|
|
"35707580709863507.json": 512,
|
|
"1258816259754246.json": 512,
|
|
"1258816259754248.json": 512,
|
|
"35707580709863489.json": 512,
|
|
"1258816259754150.json": 512,
|
|
"35707580709863494.json": 512,
|
|
"5021586753580958116.json": 512,
|
|
"35707580709863509.json": 512,
|
|
"5282957555314728059.json": 512,
|
|
"fireworks.json": 512,
|
|
"750766425144033565.json": 512,
|
|
"1258816259754276.json": 1024,
|
|
"1471004892762996753.json": 1024,
|
|
"4985886809322947159.json": 1024,
|
|
"35707580709863490.json": 1024,
|
|
"4986037051573928320.json": 512,
|
|
"1258816259754029.json": 1024,
|
|
"4987794066860147124.json": 1024,
|
|
"1258816259754212.json": 1024,
|
|
"750766425144033464.json": 1024,
|
|
"750766425144033567.json": 1024,
|
|
"1391391008142393350.json": 1024
|
|
]
|
|
|
|
let allowedDifferences: [String: Double] = [
|
|
"1258816259754165.json": 0.04
|
|
]
|
|
let defaultSize = 128
|
|
|
|
let baseCachePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + "/frame-cache"
|
|
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: baseCachePath), withIntermediateDirectories: true, attributes: nil)
|
|
print("Frame cache: \(baseCachePath)")
|
|
|
|
for (filePath, fileName) in buildAnimationFolderItems(basePath: bundlePath, path: "") {
|
|
let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: sizeMapping[fileName] ?? defaultSize, path: filePath, name: fileName)
|
|
}
|
|
|
|
var continueFromName: String?
|
|
//continueFromName = "562563904580878375.json"
|
|
|
|
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in
|
|
if let continueFromNameValue = continueFromName {
|
|
if continueFromNameValue == name {
|
|
continueFromName = nil
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
let size = sizeMapping[name] ?? defaultSize
|
|
|
|
let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), allowedDifference: allowedDifferences[name] ?? 0.01, alwaysDraw: alwaysDraw, useNonReferenceRendering: testNonReference, updateImage: { image, referenceImage, differenceImage in
|
|
DispatchQueue.main.async {
|
|
self.imageView.image = image
|
|
self.referenceImageView.image = referenceImage
|
|
self.deltaImageView.image = differenceImage
|
|
}
|
|
})
|
|
return result
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 13.0, *)
|
|
private final class ManualReferenceCompareTest {
|
|
private final class Item {
|
|
let renderer: SoftwareLottieRenderer
|
|
let referenceRenderer: ReferenceLottieAnimationItem
|
|
|
|
init(renderer: SoftwareLottieRenderer, referenceRenderer: ReferenceLottieAnimationItem) {
|
|
self.renderer = renderer
|
|
self.referenceRenderer = referenceRenderer
|
|
}
|
|
}
|
|
|
|
private let view: UIView
|
|
private let imageView = UIImageView()
|
|
private let referenceImageView = UIImageView()
|
|
private let labelView = UILabel()
|
|
|
|
private let renderSize: CGSize
|
|
private let testNonReference: Bool
|
|
|
|
private let fileList: [(filePath: String, fileName: String)]
|
|
private var currentFileIndex: Int = 0
|
|
private var currentItem: Item?
|
|
|
|
private var frameDisplayLink: SharedDisplayLinkDriver.Link?
|
|
|
|
init(view: UIView) {
|
|
self.testNonReference = true
|
|
|
|
self.currentFileIndex = 0
|
|
|
|
lottieSwift_getPathNativeBoundingBox = { path in
|
|
return getPathNativeBoundingBox(path)
|
|
}
|
|
|
|
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
|
|
self.fileList = buildAnimationFolderItems(basePath: bundlePath, path: "")
|
|
|
|
if let index = self.fileList.firstIndex(where: { $0.fileName == "shit.json" }) {
|
|
self.currentFileIndex = index
|
|
}
|
|
|
|
self.renderSize = CGSize(width: 256.0, height: 256.0)
|
|
|
|
self.view = view
|
|
self.view.backgroundColor = .white
|
|
|
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
|
|
|
let topInset: CGFloat = 50.0
|
|
|
|
self.view.addSubview(self.imageView)
|
|
self.imageView.layer.magnificationFilter = .nearest
|
|
self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0))
|
|
self.imageView.backgroundColor = self.view.backgroundColor
|
|
self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
|
|
|
|
self.view.addSubview(self.referenceImageView)
|
|
self.referenceImageView.layer.magnificationFilter = .nearest
|
|
self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
|
|
self.referenceImageView.backgroundColor = self.view.backgroundColor
|
|
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
|
|
|
|
self.view.addSubview(self.labelView)
|
|
|
|
self.updateCurrentAnimation()
|
|
}
|
|
|
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
|
if case .ended = recognizer.state {
|
|
if recognizer.location(in: self.view).x <= self.view.bounds.width * 0.5 {
|
|
if self.currentFileIndex != 0 {
|
|
self.currentFileIndex = self.currentFileIndex - 1
|
|
}
|
|
} else {
|
|
self.currentFileIndex = (self.currentFileIndex + 1) % self.fileList.count
|
|
}
|
|
self.updateCurrentAnimation()
|
|
}
|
|
}
|
|
|
|
private func updateCurrentAnimation() {
|
|
self.imageView.image = nil
|
|
self.referenceImageView.image = nil
|
|
self.currentItem = nil
|
|
|
|
self.labelView.text = "\(self.currentFileIndex + 1) / \(self.fileList.count)"
|
|
self.labelView.sizeToFit()
|
|
self.labelView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.height - 10.0 - self.labelView.bounds.height)
|
|
|
|
self.frameDisplayLink?.invalidate()
|
|
self.frameDisplayLink = nil
|
|
|
|
let (filePath, _) = self.fileList[self.currentFileIndex]
|
|
|
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
|
print("Could not load \(filePath)")
|
|
return
|
|
}
|
|
guard let renderer = SoftwareLottieRenderer(data: data) else {
|
|
print("Could not load animation at \(filePath)")
|
|
return
|
|
}
|
|
guard let referenceRenderer = ReferenceLottieAnimationItem(path: filePath) else {
|
|
print("Could not load reference animation at \(filePath)")
|
|
return
|
|
}
|
|
|
|
let currentItem = Item(renderer: renderer, referenceRenderer: referenceRenderer)
|
|
self.currentItem = currentItem
|
|
|
|
var animationTime = 0.0
|
|
let secondsPerFrame = 1.0 / Double(renderer.framesPerSecond)
|
|
|
|
let frameDisplayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
|
|
guard let self, let currentItem = self.currentItem else {
|
|
return
|
|
}
|
|
|
|
var frameIndex = animationTime / secondsPerFrame
|
|
frameIndex = frameIndex.truncatingRemainder(dividingBy: Double(currentItem.renderer.frameCount))
|
|
|
|
currentItem.renderer.setFrame(frameIndex)
|
|
let image = currentItem.renderer.render(for: self.renderSize, useReferenceRendering: !self.testNonReference, canUseMoreMemory: false, skipImageGeneration: false)!
|
|
self.imageView.image = image
|
|
|
|
currentItem.referenceRenderer.setFrame(index: Int(frameIndex))
|
|
let referenceImage = currentItem.referenceRenderer.makeImage(width: Int(self.renderSize.width), height: Int(self.renderSize.height))!
|
|
self.referenceImageView.image = referenceImage
|
|
|
|
animationTime += deltaTime
|
|
})
|
|
self.frameDisplayLink = frameDisplayLink
|
|
frameDisplayLink.isPaused = false
|
|
}
|
|
}
|
|
|
|
public final class ViewController: UIViewController {
|
|
private var link: SharedDisplayLinkDriver.Link?
|
|
private var test: AnyObject?
|
|
|
|
override public func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
SharedDisplayLinkDriver.shared.updateForegroundState(true)
|
|
|
|
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
|
|
let filePath = bundlePath + "/fire.json"
|
|
|
|
let performanceFrameSize = 128
|
|
|
|
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
|
|
|
|
if !"".isEmpty {
|
|
if #available(iOS 13.0, *) {
|
|
self.test = ReferenceCompareTest(view: self.view, testNonReference: false)
|
|
}
|
|
} else if "".isEmpty {
|
|
if #available(iOS 13.0, *) {
|
|
self.test = ManualReferenceCompareTest(view: self.view)
|
|
}
|
|
} else if !"".isEmpty {
|
|
/*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
|
|
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
|
|
|
|
/*let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath))
|
|
|
|
var startTime = CFAbsoluteTimeGetCurrent()
|
|
let animation = LottieAnimation(data: animationData)!
|
|
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
|
|
|
startTime = CFAbsoluteTimeGetCurrent()
|
|
let animationContainer = LottieAnimationContainer(animation: animation)
|
|
animationContainer.update(0)
|
|
print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")*/
|
|
|
|
let lottieLayer = LottieContentLayer(content: animation)
|
|
lottieLayer.frame = CGRect(origin: CGPoint(x: 10.0, y: 50.0), size: CGSize(width: 256.0, height: 256.0))
|
|
self.view.layer.addSublayer(lottieLayer)
|
|
lottieLayer.setNeedsUpdate()
|
|
|
|
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in
|
|
lottieLayer.frameIndex = (lottieLayer.frameIndex + 1) % animation.frameCount
|
|
lottieLayer.setNeedsUpdate()
|
|
})*/
|
|
} else if "".isEmpty {
|
|
Thread {
|
|
let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath))
|
|
|
|
var startTime = CFAbsoluteTimeGetCurrent()
|
|
|
|
let animationRenderer = SoftwareLottieRenderer(data: animationData)!
|
|
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
|
|
|
startTime = CFAbsoluteTimeGetCurrent()
|
|
var numUpdates: Int = 0
|
|
var frameIndex = 0
|
|
while true {
|
|
animationRenderer.setFrame(CGFloat(frameIndex))
|
|
let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false, canUseMoreMemory: true, skipImageGeneration: true)
|
|
frameIndex = (frameIndex + 1) % animationRenderer.frameCount
|
|
numUpdates += 1
|
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
|
let deltaTime = timestamp - startTime
|
|
if deltaTime > 2.0 {
|
|
let updatesPerSecond = Double(numUpdates) / deltaTime
|
|
startTime = timestamp
|
|
numUpdates = 0
|
|
print("Ours: updatesPerSecond: \(updatesPerSecond)")
|
|
}
|
|
}
|
|
}.start()
|
|
} else {
|
|
Thread {
|
|
var startTime = CFAbsoluteTimeGetCurrent()
|
|
let animationInstance = LottieInstance(data: try! Data(contentsOf: URL(fileURLWithPath: filePath)), fitzModifier: .none, colorReplacements: nil, cacheKey: "")!
|
|
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
|
|
|
let frameBuffer = malloc(performanceFrameSize * 4 * performanceFrameSize)!
|
|
defer {
|
|
free(frameBuffer)
|
|
}
|
|
|
|
startTime = CFAbsoluteTimeGetCurrent()
|
|
var numUpdates: Int = 0
|
|
var frameIndex = 0
|
|
while true {
|
|
animationInstance.renderFrame(with: Int32(frameIndex), into: frameBuffer, width: Int32(performanceFrameSize), height: Int32(performanceFrameSize), bytesPerRow: Int32(performanceFrameSize * 4))
|
|
|
|
frameIndex = (frameIndex + 1) % Int(animationInstance.frameCount)
|
|
numUpdates += 1
|
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
|
let deltaTime = timestamp - startTime
|
|
if deltaTime > 2.0 {
|
|
let updatesPerSecond = Double(numUpdates) / deltaTime
|
|
startTime = timestamp
|
|
numUpdates = 0
|
|
print("Rlottie: updatesPerSecond: \(updatesPerSecond)")
|
|
}
|
|
}
|
|
}.start()
|
|
}
|
|
}
|
|
}
|