mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add MetalImageView
This commit is contained in:
parent
1cda4437cc
commit
afbef025c3
18
submodules/Components/MetalImageView/BUILD
Normal file
18
submodules/Components/MetalImageView/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MetalImageView",
|
||||
module_name = "MetalImageView",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,252 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Metal
|
||||
import Display
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
open class MetalImageLayer: CALayer {
|
||||
fileprivate final class TextureStoragePool {
|
||||
let width: Int
|
||||
let height: Int
|
||||
|
||||
private var items: [TextureStorage.Content] = []
|
||||
|
||||
init(width: Int, height: Int) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
|
||||
func recycle(content: TextureStorage.Content) {
|
||||
if self.items.count < 4 {
|
||||
self.items.append(content)
|
||||
} else {
|
||||
print("Warning: over-recycling texture storage")
|
||||
}
|
||||
}
|
||||
|
||||
func take() -> TextureStorage.Content? {
|
||||
if self.items.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return self.items.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final class TextureStorage {
|
||||
final class Content {
|
||||
#if !targetEnvironment(simulator)
|
||||
let buffer: MTLBuffer
|
||||
#endif
|
||||
|
||||
let width: Int
|
||||
let height: Int
|
||||
let bytesPerRow: Int
|
||||
let texture: MTLTexture
|
||||
|
||||
init?(device: MTLDevice, width: Int, height: Int) {
|
||||
if #available(iOS 12.0, *) {
|
||||
let bytesPerPixel = 4
|
||||
let pixelRowAlignment = device.minimumLinearTextureAlignment(for: .bgra8Unorm)
|
||||
let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment)
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.bytesPerRow = bytesPerRow
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
let textureDescriptor = MTLTextureDescriptor()
|
||||
textureDescriptor.textureType = .type2D
|
||||
textureDescriptor.pixelFormat = .bgra8Unorm
|
||||
textureDescriptor.width = width
|
||||
textureDescriptor.height = height
|
||||
textureDescriptor.usage = [.renderTarget]
|
||||
textureDescriptor.storageMode = .shared
|
||||
|
||||
guard let texture = device.makeTexture(descriptor: textureDescriptor) else {
|
||||
return nil
|
||||
}
|
||||
#else
|
||||
guard let buffer = device.makeBuffer(length: bytesPerRow * height, options: MTLResourceOptions.storageModeShared) else {
|
||||
return nil
|
||||
}
|
||||
self.buffer = buffer
|
||||
|
||||
let textureDescriptor = MTLTextureDescriptor()
|
||||
textureDescriptor.textureType = .type2D
|
||||
textureDescriptor.pixelFormat = .bgra8Unorm
|
||||
textureDescriptor.width = width
|
||||
textureDescriptor.height = height
|
||||
textureDescriptor.usage = [.renderTarget]
|
||||
textureDescriptor.storageMode = buffer.storageMode
|
||||
|
||||
guard let texture = buffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: bytesPerRow) else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
|
||||
self.texture = texture
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private weak var pool: TextureStoragePool?
|
||||
let content: Content
|
||||
private var isInvalidated: Bool = false
|
||||
|
||||
init(pool: TextureStoragePool, content: Content) {
|
||||
self.pool = pool
|
||||
self.content = content
|
||||
}
|
||||
|
||||
deinit {
|
||||
if !self.isInvalidated {
|
||||
self.pool?.recycle(content: self.content)
|
||||
}
|
||||
}
|
||||
|
||||
func createCGImage() -> CGImage? {
|
||||
if self.isInvalidated {
|
||||
return nil
|
||||
}
|
||||
self.isInvalidated = true
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
guard let data = NSMutableData(capacity: self.content.bytesPerRow * self.content.height) else {
|
||||
return nil
|
||||
}
|
||||
data.length = self.content.bytesPerRow * self.content.height
|
||||
self.content.texture.getBytes(data.mutableBytes, bytesPerRow: self.content.bytesPerRow, bytesPerImage: self.content.bytesPerRow * self.content.height, from: MTLRegion(origin: MTLOrigin(), size: MTLSize(width: self.content.width, height: self.content.height, depth: 1)), mipmapLevel: 0, slice: 0)
|
||||
|
||||
guard let dataProvider = CGDataProvider(data: data as CFData) else {
|
||||
return nil
|
||||
}
|
||||
#else
|
||||
let content = self.content
|
||||
let pool = self.pool
|
||||
guard let dataProvider = CGDataProvider(data: Data(bytesNoCopy: self.content.buffer.contents(), count: self.content.buffer.length, deallocator: .custom { [weak pool] _, _ in
|
||||
guard let pool = pool else {
|
||||
return
|
||||
}
|
||||
pool.recycle(content: content)
|
||||
}) as CFData) else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
|
||||
guard let image = CGImage(
|
||||
width: Int(self.content.width),
|
||||
height: Int(self.content.height),
|
||||
bitsPerComponent: 8,
|
||||
bitsPerPixel: 8 * 4,
|
||||
bytesPerRow: self.content.bytesPerRow,
|
||||
space: DeviceGraphicsContextSettings.shared.colorSpace,
|
||||
bitmapInfo: DeviceGraphicsContextSettings.shared.transparentBitmapInfo,
|
||||
provider: dataProvider,
|
||||
decode: nil,
|
||||
shouldInterpolate: true,
|
||||
intent: .defaultIntent
|
||||
) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
public final class Drawable {
|
||||
private weak var renderer: Renderer?
|
||||
fileprivate let textureStorage: TextureStorage
|
||||
public var texture: MTLTexture {
|
||||
return self.textureStorage.content.texture
|
||||
}
|
||||
|
||||
fileprivate init(renderer: Renderer, textureStorage: TextureStorage) {
|
||||
self.renderer = renderer
|
||||
self.textureStorage = textureStorage
|
||||
}
|
||||
|
||||
public func present(completion: @escaping () -> Void) {
|
||||
self.renderer?.present(drawable: self)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
public final class Renderer {
|
||||
public var device: MTLDevice?
|
||||
private var storagePool: TextureStoragePool?
|
||||
|
||||
public var imageUpdated: ((CGImage?) -> Void)?
|
||||
|
||||
public var drawableSize: CGSize = CGSize() {
|
||||
didSet {
|
||||
if self.drawableSize != oldValue {
|
||||
if !self.drawableSize.width.isZero && !self.drawableSize.height.isZero {
|
||||
self.storagePool = TextureStoragePool(width: Int(self.drawableSize.width), height: Int(self.drawableSize.height))
|
||||
} else {
|
||||
self.storagePool = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func nextDrawable() -> Drawable? {
|
||||
guard let device = self.device else {
|
||||
return nil
|
||||
}
|
||||
guard let storagePool = self.storagePool else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let content = storagePool.take() {
|
||||
return Drawable(renderer: self, textureStorage: TextureStorage(pool: storagePool, content: content))
|
||||
} else {
|
||||
guard let content = TextureStorage.Content(device: device, width: storagePool.width, height: storagePool.height) else {
|
||||
return nil
|
||||
}
|
||||
return Drawable(renderer: self, textureStorage: TextureStorage(pool: storagePool, content: content))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func present(drawable: Drawable) {
|
||||
if let imageUpdated = self.imageUpdated {
|
||||
imageUpdated(drawable.textureStorage.createCGImage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let renderer = Renderer()
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
|
||||
self.renderer.imageUpdated = { [weak self] image in
|
||||
self?.contents = image
|
||||
}
|
||||
}
|
||||
|
||||
override public init(layer: Any) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override open func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
}
|
||||
|
||||
open class MetalImageView: UIView {
|
||||
public static override var layerClass: AnyClass {
|
||||
return MetalImageLayer.self
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user