diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift b/submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift index 9265f752ba..95d5f65081 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift @@ -20,6 +20,11 @@ public final class QueueLocalObject { } } + public func unsafeGet() -> T? { + assert(self.queue.isCurrent()) + return self.valueRef?.takeUnretainedValue() + } + public func with(_ f: @escaping (T) -> Void) { self.queue.async { if let valueRef = self.valueRef { diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift index 96920e9871..f5ca8fa4b2 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift @@ -5,10 +5,17 @@ import CryptoUtils import ManagedFile import Compression +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 +} + public final class AnimationCacheItemFrame { public enum RequestedFormat { case rgba - case yuva + case yuva(bytesPerRow: Int) } public final class Plane { @@ -279,7 +286,7 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { } else { isFirstFrame = true - surface = ImageARGB(width: width, height: height) + surface = ImageARGB(width: width, height: height, bytesPerRow: alignUp(size: width * 4, align: 32)) self.currentSurface = surface } @@ -292,7 +299,7 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { return } } else { - yuvaSurface = ImageYUVA420(width: width, height: height) + yuvaSurface = ImageYUVA420(width: width, height: height, bytesPerRow: nil) self.currentYUVASurface = yuvaSurface } @@ -484,7 +491,7 @@ private final class AnimationCacheItemAccessor { private let durationMapping: [Double] private let totalDuration: Double - private var currentYUVASurface: ImageYUVA420 + private var currentYUVASurface: ImageYUVA420? private var currentDctData: DctData private var currentDctCoefficients: DctCoefficientsYUVA420 @@ -506,7 +513,6 @@ private final class AnimationCacheItemAccessor { self.durationMapping = durationMapping self.totalDuration = totalDuration - self.currentYUVASurface = ImageYUVA420(width: width, height: height) self.currentDctData = DctData(quality: dctQuality) self.currentDctCoefficients = DctCoefficientsYUVA420(width: width, height: height) } @@ -544,43 +550,53 @@ private final class AnimationCacheItemAccessor { frameDataOffset += dctPlane.data.count } - self.currentDctCoefficients.idct(dctData: self.currentDctData, target: self.currentYUVASurface) + let yuvaSurface: ImageYUVA420 + switch requestedFormat { + case .rgba: + if let currentYUVASurface = self.currentYUVASurface { + yuvaSurface = currentYUVASurface + } else { + yuvaSurface = ImageYUVA420(width: self.currentDctCoefficients.yPlane.width, height: self.currentDctCoefficients.yPlane.height, bytesPerRow: nil) + } + case let .yuva(preferredBytesPerRow): + yuvaSurface = ImageYUVA420(width: self.currentDctCoefficients.yPlane.width, height: self.currentDctCoefficients.yPlane.height, bytesPerRow: preferredBytesPerRow) + } + + self.currentDctCoefficients.idct(dctData: self.currentDctData, target: yuvaSurface) switch requestedFormat { case .rgba: - let currentSurface = ImageARGB(width: self.currentYUVASurface.yPlane.width, height: self.currentYUVASurface.yPlane.height) - self.currentYUVASurface.toARGB(target: currentSurface) + let currentSurface = ImageARGB(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height, bytesPerRow: alignUp(size: yuvaSurface.yPlane.width * 4, align: 32)) + yuvaSurface.toARGB(target: currentSurface) + self.currentYUVASurface = yuvaSurface return AnimationCacheItemFrame(format: .rgba(data: currentSurface.argbPlane.data, width: currentSurface.argbPlane.width, height: currentSurface.argbPlane.height, bytesPerRow: currentSurface.argbPlane.bytesPerRow), duration: frameInfo.duration) case .yuva: - let currentYUVASurface = self.currentYUVASurface - self.currentYUVASurface = ImageYUVA420(width: currentYUVASurface.yPlane.width, height: currentYUVASurface.yPlane.height) - return AnimationCacheItemFrame( format: .yuva( y: AnimationCacheItemFrame.Plane( - data: currentYUVASurface.yPlane.data, - width: currentYUVASurface.yPlane.width, - height: currentYUVASurface.yPlane.height, - bytesPerRow: currentYUVASurface.yPlane.bytesPerRow + data: yuvaSurface.yPlane.data, + width: yuvaSurface.yPlane.width, + height: yuvaSurface.yPlane.height, + bytesPerRow: yuvaSurface.yPlane.bytesPerRow ), u: AnimationCacheItemFrame.Plane( - data: currentYUVASurface.uPlane.data, - width: currentYUVASurface.uPlane.width, - height: currentYUVASurface.uPlane.height, - bytesPerRow: currentYUVASurface.uPlane.bytesPerRow + data: yuvaSurface.uPlane.data, + width: yuvaSurface.uPlane.width, + height: yuvaSurface.uPlane.height, + bytesPerRow: yuvaSurface.uPlane.bytesPerRow ), v: AnimationCacheItemFrame.Plane( - data: currentYUVASurface.vPlane.data, - width: currentYUVASurface.vPlane.width, - height: currentYUVASurface.vPlane.height, - bytesPerRow: currentYUVASurface.vPlane.bytesPerRow + data: yuvaSurface.vPlane.data, + width: yuvaSurface.vPlane.width, + height: yuvaSurface.vPlane.height, + bytesPerRow: yuvaSurface.vPlane.bytesPerRow ), a: AnimationCacheItemFrame.Plane( - data: currentYUVASurface.aPlane.data, - width: currentYUVASurface.aPlane.width, - height: currentYUVASurface.aPlane.height, - bytesPerRow: currentYUVASurface.aPlane.bytesPerRow + data: yuvaSurface.aPlane.data, + width: yuvaSurface.aPlane.width, + height: yuvaSurface.aPlane.height, + bytesPerRow: yuvaSurface.aPlane.bytesPerRow ) ), duration: frameInfo.duration diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift index 284ef6aff2..add500560c 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift @@ -9,20 +9,20 @@ final class ImagePlane { let components: Int var data: Data - init(width: Int, height: Int, components: Int) { + init(width: Int, height: Int, components: Int, bytesPerRow: Int?) { self.width = width self.height = height - self.bytesPerRow = width * components + self.bytesPerRow = bytesPerRow ?? (width * components) self.components = components - self.data = Data(count: width * components * height) + self.data = Data(count: self.bytesPerRow * height) } } final class ImageARGB { let argbPlane: ImagePlane - init(width: Int, height: Int) { - self.argbPlane = ImagePlane(width: width, height: height, components: 4) + init(width: Int, height: Int, bytesPerRow: Int?) { + self.argbPlane = ImagePlane(width: width, height: height, components: 4, bytesPerRow: bytesPerRow) } } @@ -32,11 +32,11 @@ final class ImageYUVA420 { let vPlane: ImagePlane let aPlane: ImagePlane - init(width: Int, height: Int) { - self.yPlane = ImagePlane(width: width, height: height, components: 1) - self.uPlane = ImagePlane(width: width / 2, height: height / 2, components: 1) - self.vPlane = ImagePlane(width: width / 2, height: height / 2, components: 1) - self.aPlane = ImagePlane(width: width, height: height, components: 1) + init(width: Int, height: Int, bytesPerRow: Int?) { + self.yPlane = ImagePlane(width: width, height: height, components: 1, bytesPerRow: bytesPerRow) + self.uPlane = ImagePlane(width: width / 2, height: height / 2, components: 1, bytesPerRow: bytesPerRow) + self.vPlane = ImagePlane(width: width / 2, height: height / 2, components: 1, bytesPerRow: bytesPerRow) + self.aPlane = ImagePlane(width: width, height: height, components: 1, bytesPerRow: bytesPerRow) } } @@ -92,8 +92,8 @@ extension ImageARGB { } } - func toYUVA420() -> ImageYUVA420 { - let resultImage = ImageYUVA420(width: self.argbPlane.width, height: self.argbPlane.height) + func toYUVA420(bytesPerRow: Int?) -> ImageYUVA420 { + let resultImage = ImageYUVA420(width: self.argbPlane.width, height: self.argbPlane.height, bytesPerRow: bytesPerRow) self.toYUVA420(target: resultImage) return resultImage } @@ -125,8 +125,8 @@ extension ImageYUVA420 { } } - func toARGB() -> ImageARGB { - let resultImage = ImageARGB(width: self.yPlane.width, height: self.yPlane.height) + func toARGB(bytesPerRow: Int?) -> ImageARGB { + let resultImage = ImageARGB(width: self.yPlane.width, height: self.yPlane.height, bytesPerRow: bytesPerRow) self.toARGB(target: resultImage) return resultImage } @@ -215,14 +215,14 @@ extension DctCoefficientsYUVA420 { targetPlane.data.withUnsafeMutableBytes { bytes in let pixels = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self) - dctData.dct.inverse(withCoefficients: coefficients, pixels: pixels, width: sourcePlane.width, height: sourcePlane.height, coefficientsPerRow: targetPlane.width, bytesPerRow: targetPlane.width) + dctData.dct.inverse(withCoefficients: coefficients, pixels: pixels, width: sourcePlane.width, height: sourcePlane.height, coefficientsPerRow: targetPlane.width, bytesPerRow: targetPlane.bytesPerRow) } } } } - func idct(dctData: DctData) -> ImageYUVA420 { - let resultImage = ImageYUVA420(width: self.yPlane.width, height: self.yPlane.height) + func idct(dctData: DctData, bytesPerRow: Int?) -> ImageYUVA420 { + let resultImage = ImageYUVA420(width: self.yPlane.width, height: self.yPlane.height, bytesPerRow: bytesPerRow) self.idct(dctData: dctData, target: resultImage) return resultImage } diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Resources/MultiAnimationRendererShaders.metal b/submodules/TelegramUI/Components/MultiAnimationRenderer/Resources/MultiAnimationRendererShaders.metal index c8a7c8c2f7..8343f753f0 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Resources/MultiAnimationRendererShaders.metal +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Resources/MultiAnimationRendererShaders.metal @@ -21,8 +21,6 @@ vertex Varyings multiAnimationVertex( Varyings out; constant Vertex &v = verticies[vid]; - - out.position = float4(float2(v.position), 0.0, 1.0); out.texCoord = v.texCoord; @@ -37,5 +35,12 @@ fragment half4 multiAnimationFragment( texture2d textureA[[texture(3)]] ) { constexpr sampler s(address::clamp_to_edge, filter::linear); - return half4(texture.sample(s, in.texCoord)); + + half y = textureY.sample(s, in.texCoord).r; + half u = textureU.sample(s, in.texCoord).r - 0.5; + half v = textureV.sample(s, in.texCoord).r - 0.5; + half a = textureA.sample(s, in.texCoord).r; + + half4 out = half4(1.5748 * v + y, -0.1873 * v + y, 1.8556 * u + y, a); + return half4(out.b, out.g, out.r, out.a); } diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationMetalRenderer.swift b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationMetalRenderer.swift index 0f9e39af6d..4237c59b90 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationMetalRenderer.swift +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationMetalRenderer.swift @@ -62,34 +62,74 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { } private final class TextureStoragePool { - let width: Int - let height: Int - let format: TextureStorage.Content.Format + struct Parameters { + let width: Int + let height: Int + let format: TextureStorage.Content.Format + } + let parameters: Parameters private var items: [TextureStorage.Content] = [] + private var cleanupTimer: Foundation.Timer? + private var lastTakeTimestamp: Double = 0.0 init(width: Int, height: Int, format: TextureStorage.Content.Format) { - self.width = width - self.height = height - self.format = format + self.parameters = Parameters(width: width, height: height, format: format) + + let cleanupTimer = Foundation.Timer(timeInterval: 2.0, repeats: true, block: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.collect() + }) + self.cleanupTimer = cleanupTimer + RunLoop.main.add(cleanupTimer, forMode: .common) + } + + deinit { + self.cleanupTimer?.invalidate() + } + + private func collect() { + let timestamp = CFAbsoluteTimeGetCurrent() + if timestamp - self.lastTakeTimestamp < 1.0 { + return + } + if self.items.count > 32 { + autoreleasepool { + var remainingItems: [Unmanaged] = [] + while self.items.count > 32 { + let item = self.items.removeLast() + remainingItems.append(Unmanaged.passRetained(item)) + } + DispatchQueue.global().async { + autoreleasepool { + for item in remainingItems { + item.release() + } + } + } + } + } } func recycle(content: TextureStorage.Content) { - if self.items.count < 4 { - self.items.append(content) - } else { - print("Warning: over-recycling texture storage") - } + self.items.append(content) } - func take(device: MTLDevice) -> TextureStorage.Content? { + func take() -> TextureStorage? { if self.items.isEmpty { - guard let content = TextureStorage.Content(device: device, width: self.width, height: self.height, format: format) else { - return nil - } - return content + self.lastTakeTimestamp = CFAbsoluteTimeGetCurrent() + return nil } - return self.items.removeLast() + return TextureStorage(pool: self, content: self.items.removeLast()) + } + + static func takeNew(device: MTLDevice, parameters: Parameters, pool: TextureStoragePool) -> TextureStorage? { + guard let content = TextureStorage.Content(device: device, width: parameters.width, height: parameters.height, format: parameters.format) else { + return nil + } + return TextureStorage(pool: pool, content: content) } } @@ -107,6 +147,17 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { let bytesPerRow: Int let texture: MTLTexture + static func rowAlignment(device: MTLDevice, format: Format) -> Int { + let pixelFormat: MTLPixelFormat + switch format { + case .bgra: + pixelFormat = .bgra8Unorm + case .r: + pixelFormat = .r8Unorm + } + return device.minimumLinearTextureAlignment(for: pixelFormat) + } + init?(device: MTLDevice, width: Int, height: Int, format: Format) { let bytesPerPixel: Int let pixelFormat: MTLPixelFormat @@ -118,7 +169,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { bytesPerPixel = 1 pixelFormat = .r8Unorm } - let pixelRowAlignment = device.minimumLinearTextureAlignment(for: pixelFormat) + let pixelRowAlignment = Content.rowAlignment(device: device, format: format) let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment) self.width = width @@ -131,7 +182,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { textureDescriptor.pixelFormat = pixelFormat textureDescriptor.width = width textureDescriptor.height = height - textureDescriptor.usage = [.renderTarget] + textureDescriptor.usage = [.shaderRead] textureDescriptor.storageMode = .shared guard let texture = device.makeTexture(descriptor: textureDescriptor) else { @@ -149,7 +200,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { textureDescriptor.pixelFormat = pixelFormat textureDescriptor.width = width textureDescriptor.height = height - textureDescriptor.usage = [.renderTarget] + textureDescriptor.usage = [.shaderRead] textureDescriptor.storageMode = buffer.storageMode guard let texture = buffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: bytesPerRow) else { @@ -168,6 +219,8 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: width, height: height, depth: 1)) if let buffer = self.buffer, self.bytesPerRow == bytesPerRow { + assert(bytesPerRow * height <= rgbaData.count) + rgbaData.withUnsafeBytes { bytes in let _ = memcpy(buffer.contents(), bytes.baseAddress!, bytesPerRow * height) } @@ -193,64 +246,16 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { 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 - }*/ } private final class Frame { let timestamp: Double - let textureY: TextureStorage.Content - let textureU: TextureStorage.Content - let textureV: TextureStorage.Content - let textureA: TextureStorage.Content + let textureY: TextureStorage + let textureU: TextureStorage + let textureV: TextureStorage + let textureA: TextureStorage - init?(device: MTLDevice, textureY: TextureStorage.Content, textureU: TextureStorage.Content, textureV: TextureStorage.Content, textureA: TextureStorage.Content, data: AnimationCacheItemFrame, timestamp: Double) { + init?(device: MTLDevice, textureY: TextureStorage, textureU: TextureStorage, textureV: TextureStorage, textureA: TextureStorage, data: AnimationCacheItemFrame, timestamp: Double) { self.timestamp = timestamp self.textureY = textureY self.textureU = textureU @@ -261,10 +266,10 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { case .rgba: return nil case let .yuva(y, u, v, a): - self.textureY.replace(rgbaData: y.data, width: y.width, height: y.height, bytesPerRow: y.bytesPerRow) - self.textureU.replace(rgbaData: u.data, width: u.width, height: u.height, bytesPerRow: u.bytesPerRow) - self.textureV.replace(rgbaData: v.data, width: v.width, height: v.height, bytesPerRow: v.bytesPerRow) - self.textureA.replace(rgbaData: a.data, width: a.width, height: a.height, bytesPerRow: a.bytesPerRow) + self.textureY.content.replace(rgbaData: y.data, width: y.width, height: y.height, bytesPerRow: y.bytesPerRow) + self.textureU.content.replace(rgbaData: u.data, width: u.width, height: u.height, bytesPerRow: u.bytesPerRow) + self.textureV.content.replace(rgbaData: v.data, width: v.width, height: v.height, bytesPerRow: v.bytesPerRow) + self.textureA.content.replace(rgbaData: a.data, width: a.width, height: a.height, bytesPerRow: a.bytesPerRow) } } } @@ -292,9 +297,11 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { var targets: [TargetReference] = [] var slotIndex: Int + private let preferredBytesPerRow: Int - init(slotIndex: Int, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable, stateUpdated: @escaping () -> Void) { + init(slotIndex: Int, preferredBytesPerRow: Int, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable, stateUpdated: @escaping () -> Void) { self.slotIndex = slotIndex + self.preferredBytesPerRow = preferredBytesPerRow self.cache = cache self.stateUpdated = stateUpdated @@ -361,23 +368,33 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { } else if !self.isLoadingFrame { self.isLoadingFrame = true + let fullParameters = texturePoolFullPlane.parameters + let halfParameters = texturePoolHalfPlane.parameters + + let readyTextureY = texturePoolFullPlane.take() + let readyTextureU = texturePoolHalfPlane.take() + let readyTextureV = texturePoolHalfPlane.take() + let readyTextureA = texturePoolFullPlane.take() + let preferredBytesPerRow = self.preferredBytesPerRow + return LoadFrameTask(task: { [weak self] in - let frame = item.getFrame(at: timestamp, requestedFormat: .rgba) + let frame = item.getFrame(at: timestamp, requestedFormat: .yuva(bytesPerRow: preferredBytesPerRow)) + + let textureY = readyTextureY ?? TextureStoragePool.takeNew(device: device, parameters: fullParameters, pool: texturePoolFullPlane) + let textureU = readyTextureU ?? TextureStoragePool.takeNew(device: device, parameters: halfParameters, pool: texturePoolHalfPlane) + let textureV = readyTextureV ?? TextureStoragePool.takeNew(device: device, parameters: halfParameters, pool: texturePoolHalfPlane) + let textureA = readyTextureA ?? TextureStoragePool.takeNew(device: device, parameters: fullParameters, pool: texturePoolFullPlane) + + var currentFrame: Frame? + if let frame = frame, let textureY = textureY, let textureU = textureU, let textureV = textureV, let textureA = textureA { + currentFrame = Frame(device: device, textureY: textureY, textureU: textureU, textureV: textureV, textureA: textureA, data: frame, timestamp: timestamp) + } return { guard let strongSelf = self else { return } - var currentFrame: Frame? - let textureY = texturePoolFullPlane.take(device: device) - let textureU = texturePoolHalfPlane.take(device: device) - let textureV = texturePoolHalfPlane.take(device: device) - let textureA = texturePoolFullPlane.take(device: device) - if let frame = frame, let textureY = textureY, let textureU = textureU, let textureV = textureV, let textureA = textureA { - currentFrame = Frame(device: device, textureY: textureY, textureU: textureU, textureV: textureV, textureA: textureA, data: frame, timestamp: timestamp) - } - strongSelf.isLoadingFrame = false if let currentFrame = currentFrame { @@ -402,6 +419,8 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { private let texturePoolFullPlane: TextureStoragePool private let texturePoolHalfPlane: TextureStoragePool + private let preferredBytesPerRow: Int + private let slotCount: Int private let slotsX: Int private let slotsY: Int @@ -449,6 +468,8 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { self.texturePoolFullPlane = TextureStoragePool(width: Int(self.cellSize.width), height: Int(self.cellSize.height), format: .r) self.texturePoolHalfPlane = TextureStoragePool(width: Int(self.cellSize.width) / 2, height: Int(self.cellSize.height) / 2, format: .r) + self.preferredBytesPerRow = alignUp(size: Int(self.cellSize.width), align: TextureStorage.Content.rowAlignment(device: self.metalDevice, format: .r)) + super.init() self.device = self.metalDevice @@ -487,7 +508,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { for i in 0 ..< self.slotCount { if self.slotToItemId[i] == nil { self.slotToItemId[i] = itemId - self.itemContexts[itemId] = ItemContext(slotIndex: i, cache: cache, itemId: itemId, size: size, fetch: fetch, stateUpdated: { [weak self] in + self.itemContexts[itemId] = ItemContext(slotIndex: i, preferredBytesPerRow: self.preferredBytesPerRow, cache: cache, itemId: itemId, size: size, fetch: fetch, stateUpdated: { [weak self] in guard let strongSelf = self else { return } @@ -588,10 +609,15 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { } func redraw() { - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { + guard let drawable = self.nextDrawable() else { return } - guard let drawable = self.nextDrawable() else { + + let commandQueue = self.commandQueue + let renderPipelineState = self.renderPipelineState + let cellSize = self.cellSize + + guard let commandBuffer = commandQueue.makeCommandBuffer() else { return } @@ -614,7 +640,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { return } - var usedTextures: [MultiAnimationMetalRendererImpl.TextureStorage.Content] = [] + var usedTextures: [Unmanaged] = [] var vertices: [Float] = [ -1.0, -1.0, 0.0, 0.0, @@ -623,12 +649,12 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { 1.0, 1.0, 1.0, 1.0 ] - renderEncoder.setRenderPipelineState(self.renderPipelineState) + renderEncoder.setRenderPipelineState(renderPipelineState) var resolution = simd_uint2(UInt32(drawable.texture.width), UInt32(drawable.texture.height)) renderEncoder.setVertexBytes(&resolution, length: MemoryLayout.size * 2, index: 1) - var slotSize = simd_uint2(UInt32(self.cellSize.width), UInt32(self.cellSize.height)) + var slotSize = simd_uint2(UInt32(cellSize.width), UInt32(cellSize.height)) renderEncoder.setVertexBytes(&slotSize, length: MemoryLayout.size * 2, index: 2) for (_, itemContext) in self.itemContexts { @@ -660,14 +686,14 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { var slotPosition = simd_uint2(UInt32(itemContext.slotIndex % self.slotsX), UInt32(itemContext.slotIndex % self.slotsY)) renderEncoder.setVertexBytes(&slotPosition, length: MemoryLayout.size * 2, index: 3) - usedTextures.append(frame.textureY) - usedTextures.append(frame.textureU) - usedTextures.append(frame.textureV) - usedTextures.append(frame.textureA) - renderEncoder.setFragmentTexture(frame.textureY.texture, index: 0) - renderEncoder.setFragmentTexture(frame.textureU.texture, index: 1) - renderEncoder.setFragmentTexture(frame.textureV.texture, index: 2) - renderEncoder.setFragmentTexture(frame.textureA.texture, index: 3) + usedTextures.append(Unmanaged.passRetained(frame.textureY)) + usedTextures.append(Unmanaged.passRetained(frame.textureU)) + usedTextures.append(Unmanaged.passRetained(frame.textureV)) + usedTextures.append(Unmanaged.passRetained(frame.textureA)) + renderEncoder.setFragmentTexture(frame.textureY.content.texture, index: 0) + renderEncoder.setFragmentTexture(frame.textureU.content.texture, index: 1) + renderEncoder.setFragmentTexture(frame.textureV.content.texture, index: 2) + renderEncoder.setFragmentTexture(frame.textureA.content.texture, index: 3) renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) } @@ -692,7 +718,8 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { } commandBuffer.addCompletedHandler { _ in DispatchQueue.main.async { - for _ in usedTextures { + for texture in usedTextures { + texture.release() } } } @@ -820,13 +847,13 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer { for completion in completions { completion() } - } - } - - if let strongSelf = self { - for index in surfaceLayersWithTasks { - if let surfaceLayer = strongSelf.surfaceLayers[index] { - surfaceLayer.redraw() + + if let strongSelf = self { + for index in surfaceLayersWithTasks { + if let surfaceLayer = strongSelf.surfaceLayers[index] { + surfaceLayer.redraw() + } + } } } }