Metal rendering improvements

This commit is contained in:
Ali 2022-02-18 20:07:42 +04:00
parent b90e1bcef6
commit 1cda4437cc
5 changed files with 53 additions and 202 deletions

View File

@ -245,6 +245,12 @@ public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
} }
} }
private func alignUp(size: Int, align: Int) -> Int {
precondition(((align - 1) & align) == 0, "Align must be a power of two")
let alignmentMask = align - 1
return (size + alignmentMask) & ~alignmentMask
}
private final class AnimatedStickerDirectFrameSourceCache { private final class AnimatedStickerDirectFrameSourceCache {
private enum FrameRangeResult { private enum FrameRangeResult {
@ -274,8 +280,8 @@ private final class AnimatedStickerDirectFrameSourceCache {
self.storeQueue = sharedStoreQueue self.storeQueue = sharedStoreQueue
self.frameCount = frameCount self.frameCount = frameCount
self.width = width self.width = alignUp(size: width, align: 8)
self.height = height self.height = alignUp(size: width, align: 8)
self.useHardware = useHardware self.useHardware = useHardware
let suffix : String let suffix : String

View File

@ -248,7 +248,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
override public func didLoad() { override public func didLoad() {
super.didLoad() super.didLoad()
if #available(iOS 10.0, *), self.useMetalCache { if #available(iOS 10.0, *), (self.useMetalCache/* || "".isEmpty*/) {
self.renderer = AnimatedStickerNode.hardwareRendererPool.take() self.renderer = AnimatedStickerNode.hardwareRendererPool.take()
} else { } else {
self.renderer = AnimatedStickerNode.softwareRendererPool.take() self.renderer = AnimatedStickerNode.softwareRendererPool.take()

View File

@ -42,9 +42,9 @@ final class AnimationRendererPool {
} }
private func putBack(renderer: AnimationRenderer) { private func putBack(renderer: AnimationRenderer) {
#if DEBUG /*#if DEBUG
self.items.append(renderer) self.items.append(renderer)
#endif #endif*/
} }
} }

View File

@ -8,44 +8,19 @@ import Accelerate
import AnimationCompression import AnimationCompression
import Metal import Metal
import MetalKit import MetalKit
import MetalImageView
@available(iOS 10.0, *) @available(iOS 10.0, *)
final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer { final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer {
private final class View: UIView { private final class View: UIView {
static override var layerClass: AnyClass { static override var layerClass: AnyClass {
#if targetEnvironment(simulator) return MetalImageLayer.self
if #available(iOS 13.0, *) {
return CAMetalLayer.self
} else {
preconditionFailure()
}
#else
return CAMetalLayer.self
#endif
} }
init(device: MTLDevice) { init(device: MTLDevice) {
super.init(frame: CGRect()) super.init(frame: CGRect())
#if targetEnvironment(simulator) (self.layer as! MetalImageLayer).renderer.device = device
if #available(iOS 13.0, *) {
let metalLayer = self.layer as! CAMetalLayer
metalLayer.device = MTLCreateSystemDefaultDevice()
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true
metalLayer.allowsNextDrawableTimeout = true
}
#else
let metalLayer = self.layer as! CAMetalLayer
metalLayer.device = MTLCreateSystemDefaultDevice()
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true
if #available(iOS 11.0, *) {
metalLayer.allowsNextDrawableTimeout = true
}
#endif
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -82,16 +57,25 @@ final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer {
func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) { func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) {
switch type { switch type {
case .dct: case .dct:
self.renderer.renderIdct(metalLayer: self.layer, compressedImage: AnimationCompressor.CompressedImageData(data: data), completion: completion) self.renderer.renderIdct(layer: self.layer as! MetalImageLayer, compressedImage: AnimationCompressor.CompressedImageData(data: data), completion: { [weak self] in
self?.updateHighlightedContentNode()
completion()
})
case .argb: case .argb:
self.renderer.renderRgb(metalLayer: self.layer, width: width, height: height, bytesPerRow: bytesPerRow, data: data, completion: completion) self.renderer.renderRgb(layer: self.layer as! MetalImageLayer, width: width, height: height, bytesPerRow: bytesPerRow, data: data, completion: { [weak self] in
self?.updateHighlightedContentNode()
completion()
})
case .yuva: case .yuva:
self.renderer.renderYuva(metalLayer: self.layer, width: width, height: height, data: data, completion: completion) self.renderer.renderYuva(layer: self.layer as! MetalImageLayer, width: width, height: height, data: data, completion: { [weak self] in
self?.updateHighlightedContentNode()
completion()
})
} }
} }
private func updateHighlightedContentNode() { private func updateHighlightedContentNode() {
/*guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else { guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else {
return return
} }
if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID { if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
@ -100,11 +84,11 @@ final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer {
highlightedContentNode.tintColor = highlightedColor highlightedContentNode.tintColor = highlightedColor
if self.highlightReplacesContent { if self.highlightReplacesContent {
self.contents = nil self.contents = nil
}*/ }
} }
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
/*self.highlightReplacesContent = replace self.highlightReplacesContent = replace
var updated = false var updated = false
if let current = self.highlightedColor, let color = color { if let current = self.highlightedColor, let color = color {
updated = !current.isEqual(color) updated = !current.isEqual(color)
@ -141,6 +125,6 @@ final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer {
strongSelf.highlightedContentNode?.removeFromSupernode() strongSelf.highlightedContentNode?.removeFromSupernode()
strongSelf.highlightedContentNode = nil strongSelf.highlightedContentNode = nil
}) })
}*/ }
} }
} }

View File

@ -4,6 +4,7 @@ import Metal
import MetalKit import MetalKit
import simd import simd
import DctHuffman import DctHuffman
import MetalImageView
private struct Vertex { private struct Vertex {
var position: vector_float2 var position: vector_float2
@ -109,62 +110,9 @@ public final class CompressedImageRenderer {
private var drawableRequestTimestamp: Double? private var drawableRequestTimestamp: Double?
private func getNextDrawable(metalLayer: CALayer, drawableSize: CGSize) -> CAMetalDrawable? { private func getNextDrawable(layer: MetalImageLayer, drawableSize: CGSize) -> MetalImageLayer.Drawable? {
#if targetEnvironment(simulator) layer.renderer.drawableSize = drawableSize
if #available(iOS 13.0, *) { return layer.renderer.nextDrawable()
if let metalLayer = metalLayer as? CAMetalLayer {
if metalLayer.drawableSize != drawableSize {
metalLayer.drawableSize = drawableSize
}
let beginTime = CFAbsoluteTimeGetCurrent()
let drawableRequestDuration: Double
if let drawableRequestTimestamp = self.drawableRequestTimestamp {
drawableRequestDuration = beginTime - drawableRequestTimestamp
if drawableRequestDuration < 1.0 / 60.0 {
return nil
}
} else {
drawableRequestDuration = 0.0
}
self.drawableRequestTimestamp = beginTime
let result = metalLayer.nextDrawable()
let duration = CFAbsoluteTimeGetCurrent() - beginTime
if duration > 1.0 / 60.0 {
print("lag \(duration * 1000.0) ms (\(drawableRequestDuration * 1000.0) ms)")
}
return result
} else {
return nil
}
} else {
return nil
}
#else
if let metalLayer = metalLayer as? CAMetalLayer {
if metalLayer.drawableSize != drawableSize {
metalLayer.drawableSize = drawableSize
}
let beginTime = CFAbsoluteTimeGetCurrent()
let drawableRequestDuration: Double
if let drawableRequestTimestamp = self.drawableRequestTimestamp {
drawableRequestDuration = beginTime - drawableRequestTimestamp
if drawableRequestDuration < 1.0 / 60.0 {
return nil
}
} else {
drawableRequestDuration = 0.0
}
self.drawableRequestTimestamp = beginTime
let result = metalLayer.nextDrawable()
let duration = CFAbsoluteTimeGetCurrent() - beginTime
if duration > 1.0 / 200.0 {
print("lag \(duration * 1000.0) ms (\(drawableRequestDuration * 1000.0) ms)")
}
return result
} else {
return nil
}
#endif
} }
private func updateIdctTextures(compressedImage: AnimationCompressor.CompressedImageData) { private func updateIdctTextures(compressedImage: AnimationCompressor.CompressedImageData) {
@ -241,7 +189,7 @@ public final class CompressedImageRenderer {
} }
} }
public func renderIdct(metalLayer: CALayer, compressedImage: AnimationCompressor.CompressedImageData, completion: @escaping () -> Void) { public func renderIdct(layer: MetalImageLayer, compressedImage: AnimationCompressor.CompressedImageData, completion: @escaping () -> Void) {
DispatchQueue.global().async { DispatchQueue.global().async {
self.updateIdctTextures(compressedImage: compressedImage) self.updateIdctTextures(compressedImage: compressedImage)
@ -313,7 +261,7 @@ public final class CompressedImageRenderer {
let drawableSize = CGSize(width: CGFloat(outputTextures.textures[0].width), height: CGFloat(outputTextures.textures[0].height)) let drawableSize = CGSize(width: CGFloat(outputTextures.textures[0].width), height: CGFloat(outputTextures.textures[0].height))
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else { guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit() commandBuffer.commit()
completion() completion()
return return
@ -339,35 +287,15 @@ public final class CompressedImageRenderer {
renderEncoder.endEncoding() renderEncoder.endEncoding()
var storedDrawable: MTLDrawable? = drawable var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addScheduledHandler { _ in
autoreleasepool {
storedDrawable?.present()
storedDrawable = nil
}
}
#if targetEnvironment(simulator)
commandBuffer.addCompletedHandler { _ in commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async { DispatchQueue.main.async {
completion() autoreleasepool {
} storedDrawable?.present(completion: completion)
} storedDrawable = nil
#else
if #available(iOS 10.3, *) {
drawable.addPresentedHandler { _ in
DispatchQueue.main.async {
completion()
}
}
} else {
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
completion()
} }
} }
} }
#endif
commandBuffer.commit() commandBuffer.commit()
} }
@ -402,14 +330,7 @@ public final class CompressedImageRenderer {
}) })
} }
public func renderRgb(metalLayer: CALayer, width: Int, height: Int, bytesPerRow: Int, data: Data, completion: @escaping () -> Void) { public func renderRgb(layer: MetalImageLayer, width: Int, height: Int, bytesPerRow: Int, data: Data, completion: @escaping () -> Void) {
if "".isEmpty {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0 / 60.0, execute: {
completion()
})
return
}
self.updateRgbTexture(width: width, height: height, bytesPerRow: bytesPerRow, data: data) self.updateRgbTexture(width: width, height: height, bytesPerRow: bytesPerRow, data: data)
guard let rgbTexture = self.rgbTexture else { guard let rgbTexture = self.rgbTexture else {
@ -423,7 +344,7 @@ public final class CompressedImageRenderer {
let drawableSize = CGSize(width: CGFloat(rgbTexture.width), height: CGFloat(rgbTexture.height)) let drawableSize = CGSize(width: CGFloat(rgbTexture.width), height: CGFloat(rgbTexture.height))
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else { guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit() commandBuffer.commit()
completion() completion()
return return
@ -446,35 +367,15 @@ public final class CompressedImageRenderer {
renderEncoder.endEncoding() renderEncoder.endEncoding()
var storedDrawable: MTLDrawable? = drawable var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addScheduledHandler { _ in
autoreleasepool {
storedDrawable?.present()
storedDrawable = nil
}
}
#if targetEnvironment(simulator)
commandBuffer.addCompletedHandler { _ in commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async { DispatchQueue.main.async {
completion() autoreleasepool {
} storedDrawable?.present(completion: completion)
} storedDrawable = nil
#else
if #available(iOS 10.3, *) {
drawable.addPresentedHandler { _ in
DispatchQueue.main.async {
completion()
}
}
} else {
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
completion()
} }
} }
} }
#endif
commandBuffer.commit() commandBuffer.commit()
} }
@ -553,17 +454,10 @@ public final class CompressedImageRenderer {
} }
} }
public func renderYuva(metalLayer: CALayer, width: Int, height: Int, data: Data, completion: @escaping () -> Void) { public func renderYuva(layer: MetalImageLayer, width: Int, height: Int, data: Data, completion: @escaping () -> Void) {
if self.isRendering {
DispatchQueue.main.async {
completion()
}
return
}
self.isRendering = true
DispatchQueue.global().async { DispatchQueue.global().async {
autoreleasepool { autoreleasepool {
let renderStartTime = CFAbsoluteTimeGetCurrent() //let renderStartTime = CFAbsoluteTimeGetCurrent()
var beginTime: Double = 0.0 var beginTime: Double = 0.0
var duration: Double = 0.0 var duration: Double = 0.0
@ -578,7 +472,6 @@ public final class CompressedImageRenderer {
guard let yuvaTextures = self.yuvaTextures else { guard let yuvaTextures = self.yuvaTextures else {
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRendering = false
completion() completion()
} }
return return
@ -588,7 +481,6 @@ public final class CompressedImageRenderer {
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRendering = false
completion() completion()
} }
return return
@ -598,10 +490,9 @@ public final class CompressedImageRenderer {
let drawableSize = CGSize(width: CGFloat(yuvaTextures.width), height: CGFloat(yuvaTextures.height)) let drawableSize = CGSize(width: CGFloat(yuvaTextures.width), height: CGFloat(yuvaTextures.height))
guard let drawable = self.getNextDrawable(metalLayer: metalLayer, drawableSize: drawableSize) else { guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit() commandBuffer.commit()
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRendering = false
completion() completion()
} }
return return
@ -614,7 +505,6 @@ public final class CompressedImageRenderer {
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRendering = false
completion() completion()
} }
return return
@ -633,44 +523,15 @@ public final class CompressedImageRenderer {
renderEncoder.endEncoding() renderEncoder.endEncoding()
var storedDrawable: MTLDrawable? = drawable var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addScheduledHandler { _ in
autoreleasepool {
storedDrawable?.present()
storedDrawable = nil
}
}
#if targetEnvironment(simulator)
commandBuffer.addCompletedHandler { _ in commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRendering = false autoreleasepool {
completion() storedDrawable?.present(completion: completion)
} storedDrawable = nil
}
#else
if #available(iOS 10.3, *) {
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
self.isRendering = false
}
let renderDuration = CFAbsoluteTimeGetCurrent() - renderStartTime
print("render duration \(renderDuration * 1000.0) ms")
}
drawable.addPresentedHandler { _ in
DispatchQueue.main.async {
completion()
}
}
} else {
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
self.isRendering = false
completion()
} }
} }
} }
#endif
commandBuffer.commit() commandBuffer.commit()