GLEGram 12.5 — Initial public release

Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,393 @@
import Foundation
import Metal
import DctHuffman
private final class BundleHelper: NSObject {
}
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
}
final class Texture {
final class DirectBuffer {
let buffer: MTLBuffer
let bytesPerRow: Int
init?(device: MTLDevice, width: Int, height: Int, bytesPerRow: Int) {
#if targetEnvironment(simulator)
return nil
#else
if #available(iOS 12.0, *) {
let pagesize = Int(getpagesize())
let allocationSize = alignUp(size: bytesPerRow * height, align: pagesize)
var data: UnsafeMutableRawPointer? = nil
let result = posix_memalign(&data, pagesize, allocationSize)
if result == noErr, let data = data {
self.bytesPerRow = bytesPerRow
guard let buffer = device.makeBuffer(
bytesNoCopy: data,
length: allocationSize,
options: .storageModeShared,
deallocator: { _, _ in
free(data)
}
) else {
return nil
}
self.buffer = buffer
} else {
return nil
}
} else {
return nil
}
#endif
}
}
let width: Int
let height: Int
let texture: MTLTexture
let directBuffer: DirectBuffer?
init?(
device: MTLDevice,
width: Int,
height: Int,
pixelFormat: MTLPixelFormat,
usage: MTLTextureUsage,
isShared: Bool
) {
self.width = width
self.height = height
if #available(iOS 12.0, *), isShared, usage.contains(.shaderRead) {
switch pixelFormat {
case .r32Float, .bgra8Unorm:
let bytesPerPixel = 4
let pixelRowAlignment = device.minimumTextureBufferAlignment(for: pixelFormat)
let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment)
self.directBuffer = DirectBuffer(device: device, width: width, height: height, bytesPerRow: bytesPerRow)
case .r8Unorm, .r8Uint:
let bytesPerPixel = 1
let pixelRowAlignment = device.minimumTextureBufferAlignment(for: pixelFormat)
let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment)
self.directBuffer = DirectBuffer(device: device, width: width, height: height, bytesPerRow: bytesPerRow)
case .rg8Unorm:
let bytesPerPixel = 2
let pixelRowAlignment = device.minimumTextureBufferAlignment(for: pixelFormat)
let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment)
self.directBuffer = DirectBuffer(device: device, width: width, height: height, bytesPerRow: bytesPerRow)
default:
self.directBuffer = nil
}
} else {
self.directBuffer = nil
}
let textureDescriptor = MTLTextureDescriptor()
textureDescriptor.textureType = .type2D
textureDescriptor.pixelFormat = pixelFormat
textureDescriptor.width = width
textureDescriptor.height = height
textureDescriptor.usage = usage
if let directBuffer = self.directBuffer {
textureDescriptor.storageMode = directBuffer.buffer.storageMode
guard let texture = directBuffer.buffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: directBuffer.bytesPerRow) else {
return nil
}
self.texture = texture
} else {
guard let texture = device.makeTexture(descriptor: textureDescriptor) else {
return nil
}
self.texture = texture
}
}
func replace(with image: AnimationCompressor.ImageData) {
if image.width != self.width || image.height != self.height {
assert(false, "Image size does not match")
return
}
let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: image.width, height: image.height, depth: 1))
if let directBuffer = self.directBuffer, directBuffer.bytesPerRow == image.bytesPerRow {
image.data.withUnsafeBytes { bytes in
let _ = memcpy(directBuffer.buffer.contents(), bytes.baseAddress!, image.bytesPerRow * self.height)
}
} else {
image.data.withUnsafeBytes { bytes in
self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes.baseAddress!, bytesPerRow: image.bytesPerRow)
}
}
}
func readDirect(width: Int, height: Int, bytesPerRow: Int, read: (UnsafeMutableRawPointer?) -> UnsafeRawPointer) {
if let directBuffer = self.directBuffer, width == self.width, height == self.height, bytesPerRow == directBuffer.bytesPerRow {
let _ = read(directBuffer.buffer.contents())
} else {
let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: width, height: height, depth: 1))
self.texture.replace(region: region, mipmapLevel: 0, withBytes: read(nil), bytesPerRow: bytesPerRow)
}
}
}
final class TextureSet {
struct Description {
let fractionWidth: Int
let fractionHeight: Int
let pixelFormat: MTLPixelFormat
}
let width: Int
let height: Int
let textures: [Texture]
init?(
device: MTLDevice,
width: Int,
height: Int,
descriptions: [Description],
usage: MTLTextureUsage,
isShared: Bool
) {
self.width = width
self.height = height
var textures: [Texture] = []
for i in 0 ..< descriptions.count {
let planeWidth = width / descriptions[i].fractionWidth
let planeHeight = height / descriptions[i].fractionHeight
guard let texture = Texture(
device: device,
width: planeWidth,
height: planeHeight,
pixelFormat: descriptions[i].pixelFormat,
usage: usage,
isShared: isShared
) else {
return nil
}
textures.append(texture)
}
self.textures = textures
}
}
public final class AnimationCompressor {
public final class ImageData {
public let width: Int
public let height: Int
public let bytesPerRow: Int
public let data: Data
public init(width: Int, height: Int, bytesPerRow: Int, data: Data) {
self.width = width
self.height = height
self.bytesPerRow = bytesPerRow
self.data = data
}
}
public final class CompressedImageData {
public let data: Data
public init(data: Data) {
self.data = data
}
}
public final class SharedContext {
public static let shared: SharedContext = SharedContext()!
public let device: MTLDevice
let defaultLibrary: MTLLibrary
private let computeDctPipelineState: MTLComputePipelineState
private let commandQueue: MTLCommandQueue
public init?() {
guard let device = MTLCreateSystemDefaultDevice() else {
return nil
}
self.device = device
let mainBundle = Bundle(for: BundleHelper.self)
guard let path = mainBundle.path(forResource: "AnimationCompressionBundle", ofType: "bundle") else {
return nil
}
guard let bundle = Bundle(path: path) else {
return nil
}
if #available(iOS 10.0, *) {
guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else {
return nil
}
self.defaultLibrary = defaultLibrary
} else {
preconditionFailure()
}
guard let dctFunction = self.defaultLibrary.makeFunction(name: "dctKernel") else {
return nil
}
guard let computeDctPipelineState = try? self.device.makeComputePipelineState(function: dctFunction) else {
return nil
}
self.computeDctPipelineState = computeDctPipelineState
guard let commandQueue = self.device.makeCommandQueue() else {
return nil
}
self.commandQueue = commandQueue
}
func compress(compressor: AnimationCompressor, image: ImageData, completion: @escaping (CompressedImageData) -> Void) {
let threadgroupSize = MTLSize(width: 8, height: 8, depth: 1)
assert(image.width % 8 == 0)
assert(image.height % 8 == 0)
let inputTexture: Texture
if let current = compressor.inputTexture, current.width == image.width, current.height == image.height {
inputTexture = current
} else {
guard let texture = Texture(
device: self.device,
width: image.width,
height: image.height,
pixelFormat: .bgra8Unorm,
usage: .shaderRead,
isShared: true
) else {
return
}
inputTexture = texture
compressor.inputTexture = texture
}
inputTexture.replace(with: image)
let compressedTextures: TextureSet
if let current = compressor.compressedTextures, current.width == image.width, current.height == image.height {
compressedTextures = current
} else {
guard let textures = TextureSet(
device: self.device,
width: image.width,
height: image.height,
descriptions: [
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r32Float
)
],
usage: [.shaderWrite],
isShared: false
) else {
return
}
compressedTextures = textures
compressor.compressedTextures = textures
}
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
return
}
commandBuffer.label = "ImageCompressor"
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
return
}
computeEncoder.setComputePipelineState(self.computeDctPipelineState)
computeEncoder.setTexture(inputTexture.texture, index: 0)
for colorPlane in 0 ..< 4 {
computeEncoder.setTexture(compressedTextures.textures[colorPlane].texture, index: 1)
var colorPlaneInt32 = Int32(colorPlane)
computeEncoder.setBytes(&colorPlaneInt32, length: 4, index: 2)
let threadgroupCount = MTLSize(width: (compressedTextures.textures[colorPlane].width + threadgroupSize.width - 1) / threadgroupSize.width, height: (compressedTextures.textures[colorPlane].height + threadgroupSize.height - 1) / threadgroupSize.height, depth: 1)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}
computeEncoder.endEncoding()
commandBuffer.addCompletedHandler { _ in
let buffer = WriteBuffer()
buffer.writeInt32(0x543ee445)
buffer.writeInt32(4)
buffer.writeInt32(Int32(compressedTextures.textures[0].width))
buffer.writeInt32(Int32(compressedTextures.textures[0].height))
for i in 0 ..< 4 {
let region = MTLRegion(origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: compressedTextures.textures[i].width, height: compressedTextures.textures[i].height, depth: 1))
let bytesPerRow = 4 * compressedTextures.textures[i].width
buffer.writeInt32(Int32(compressedTextures.textures[i].width))
buffer.writeInt32(Int32(compressedTextures.textures[i].height))
buffer.writeInt32(Int32(bytesPerRow))
var textureBytes = Data(count: bytesPerRow * compressedTextures.textures[i].height)
textureBytes.withUnsafeMutableBytes { bytes in
compressedTextures.textures[i].texture.getBytes(bytes.baseAddress!, bytesPerRow: bytesPerRow, bytesPerImage: bytesPerRow * compressedTextures.textures[i].height, from: region, mipmapLevel: 0, slice: 0)
let huffmanData = writeDCTBlocks(Int32(compressedTextures.textures[i].width), Int32(compressedTextures.textures[i].height), bytes.baseAddress!.assumingMemoryBound(to: Float32.self))!
buffer.writeInt32(Int32(huffmanData.count))
buffer.write(huffmanData)
}
}
DispatchQueue.main.async {
completion(CompressedImageData(data: buffer.makeData()))
}
}
commandBuffer.commit()
}
}
private let sharedContext: SharedContext
private var inputTexture: Texture?
private var compressedTextures: TextureSet?
public init(sharedContext: SharedContext) {
self.sharedContext = sharedContext
}
public func compress(image: ImageData, completion: @escaping (CompressedImageData) -> Void) {
self.sharedContext.compress(compressor: self, image: image, completion: completion)
}
}
@@ -0,0 +1,110 @@
import Foundation
class MemoryBuffer {
var data: Data
var length: Int
init(data: Data) {
self.data = data
self.length = data.count
}
}
final class WriteBuffer: MemoryBuffer {
var offset = 0
init() {
super.init(data: Data())
}
func makeData() -> Data {
return self.data
}
func reset() {
self.offset = 0
}
func write(_ data: UnsafeRawPointer, offset: Int = 0, length: Int) {
if self.offset + length > self.data.count {
self.data.count = self.offset + length + 256
}
self.data.withUnsafeMutableBytes { bytes in
let _ = memcpy(bytes.baseAddress!.advanced(by: self.offset), data + offset, length)
}
self.offset += length
self.length = self.offset
}
func write(_ data: Data) {
data.withUnsafeBytes { bytes in
self.write(bytes.baseAddress!, length: bytes.count)
}
}
func writeInt8(_ value: Int8) {
var value = value
self.write(&value, length: 1)
}
func writeInt32(_ value: Int32) {
var value = value
self.write(&value, length: 4)
}
func writeFloat(_ value: Float) {
var value: Float32 = value
self.write(&value, length: 4)
}
func seek(offset: Int) {
self.offset = offset
}
}
final class ReadBuffer: MemoryBuffer {
var offset = 0
override init(data: Data) {
super.init(data: data)
}
func read(_ data: UnsafeMutableRawPointer, length: Int) {
self.data.copyBytes(to: data.assumingMemoryBound(to: UInt8.self), from: self.offset ..< (self.offset + length))
self.offset += length
}
func readDataNoCopy(length: Int) -> Data {
let result = self.data.withUnsafeBytes { bytes -> Data in
return Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: self.offset)), count: length, deallocator: .none)
}
self.offset += length
return result
}
func readInt8() -> Int8 {
var result: Int8 = 0
self.read(&result, length: 1)
return result
}
func readInt32() -> Int32 {
var result: Int32 = 0
self.read(&result, length: 4)
return result
}
func readFloat() -> Float {
var result: Float32 = 0
self.read(&result, length: 4)
return result
}
func skip(_ length: Int) {
self.offset += length
}
func reset() {
self.offset = 0
}
}
@@ -0,0 +1,545 @@
import Foundation
import UIKit
import Metal
import MetalKit
import simd
import DctHuffman
import MetalImageView
private struct Vertex {
var position: vector_float2
var textureCoordinate: vector_float2
}
public final class CompressedImageRenderer {
private final class Shared {
static let shared: Shared = {
return Shared(sharedContext: AnimationCompressor.SharedContext.shared)!
}()
let sharedContext: AnimationCompressor.SharedContext
let computeIdctPipelineState: MTLComputePipelineState
let renderIdctPipelineState: MTLRenderPipelineState
let renderRgbPipelineState: MTLRenderPipelineState
let renderYuvaPipelineState: MTLRenderPipelineState
init?(sharedContext: AnimationCompressor.SharedContext) {
self.sharedContext = sharedContext
guard let idctFunction = self.sharedContext.defaultLibrary.makeFunction(name: "idctKernel") else {
return nil
}
guard let computeIdctPipelineState = try? self.sharedContext.device.makeComputePipelineState(function: idctFunction) else {
return nil
}
self.computeIdctPipelineState = computeIdctPipelineState
guard let vertexShader = self.sharedContext.defaultLibrary.makeFunction(name: "vertexShader") else {
return nil
}
guard let samplingIdctShader = self.sharedContext.defaultLibrary.makeFunction(name: "samplingIdctShader") else {
return nil
}
guard let samplingRgbShader = self.sharedContext.defaultLibrary.makeFunction(name: "samplingRgbShader") else {
return nil
}
guard let samplingYuvaShader = self.sharedContext.defaultLibrary.makeFunction(name: "samplingYuvaShader") else {
return nil
}
let idctPipelineStateDescriptor = MTLRenderPipelineDescriptor()
idctPipelineStateDescriptor.label = "Render IDCT Pipeline"
idctPipelineStateDescriptor.vertexFunction = vertexShader
idctPipelineStateDescriptor.fragmentFunction = samplingIdctShader
idctPipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
guard let renderIdctPipelineState = try? self.sharedContext.device.makeRenderPipelineState(descriptor: idctPipelineStateDescriptor) else {
return nil
}
self.renderIdctPipelineState = renderIdctPipelineState
let rgbPipelineStateDescriptor = MTLRenderPipelineDescriptor()
rgbPipelineStateDescriptor.label = "Render RGB Pipeline"
rgbPipelineStateDescriptor.vertexFunction = vertexShader
rgbPipelineStateDescriptor.fragmentFunction = samplingRgbShader
rgbPipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
guard let renderRgbPipelineState = try? self.sharedContext.device.makeRenderPipelineState(descriptor: rgbPipelineStateDescriptor) else {
return nil
}
self.renderRgbPipelineState = renderRgbPipelineState
let yuvaPipelineStateDescriptor = MTLRenderPipelineDescriptor()
yuvaPipelineStateDescriptor.label = "Render YUVA Pipeline"
yuvaPipelineStateDescriptor.vertexFunction = vertexShader
yuvaPipelineStateDescriptor.fragmentFunction = samplingYuvaShader
yuvaPipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
guard let renderYuvaPipelineState = try? self.sharedContext.device.makeRenderPipelineState(descriptor: yuvaPipelineStateDescriptor) else {
return nil
}
self.renderYuvaPipelineState = renderYuvaPipelineState
}
}
private let sharedContext: AnimationCompressor.SharedContext
private let shared: Shared
private var compressedTextures: TextureSet?
private var outputTextures: TextureSet?
private var rgbTexture: Texture?
private var yuvaTextures: TextureSet?
private let commandQueue: MTLCommandQueue
private var isRendering: Bool = false
public init?(sharedContext: AnimationCompressor.SharedContext) {
self.sharedContext = sharedContext
self.shared = Shared.shared
guard let commandQueue = self.sharedContext.device.makeCommandQueue() else {
return nil
}
self.commandQueue = commandQueue
}
private var drawableRequestTimestamp: Double?
private func getNextDrawable(layer: MetalImageLayer, drawableSize: CGSize) -> MetalImageLayer.Drawable? {
layer.renderer.drawableSize = drawableSize
return layer.renderer.nextDrawable()
}
private func updateIdctTextures(compressedImage: AnimationCompressor.CompressedImageData) {
self.rgbTexture = nil
self.yuvaTextures = nil
let readBuffer = ReadBuffer(data: compressedImage.data)
if readBuffer.readInt32() != 0x543ee445 {
return
}
if readBuffer.readInt32() != 4 {
return
}
let width = Int(readBuffer.readInt32())
let height = Int(readBuffer.readInt32())
let compressedTextures: TextureSet
if let current = self.compressedTextures, current.width == width, current.height == height {
compressedTextures = current
} else {
guard let textures = TextureSet(
device: self.sharedContext.device,
width: width,
height: height,
descriptions: [
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r32Float
),
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r32Float
)
],
usage: .shaderRead,
isShared: true
) else {
return
}
self.compressedTextures = textures
compressedTextures = textures
}
for i in 0 ..< 4 {
let planeWidth = Int(readBuffer.readInt32())
let planeHeight = Int(readBuffer.readInt32())
let bytesPerRow = Int(readBuffer.readInt32())
let planeSize = Int(readBuffer.readInt32())
let planeData = readBuffer.readDataNoCopy(length: planeSize)
var tempData: Data?
compressedTextures.textures[i].readDirect(width: planeWidth, height: planeHeight, bytesPerRow: bytesPerRow, read: { destinationBytes in
if let destinationBytes = destinationBytes {
readDCTBlocks(Int32(planeWidth), Int32(planeHeight), planeData, destinationBytes.assumingMemoryBound(to: Float32.self), Int32(bytesPerRow / 4))
return UnsafeRawPointer(destinationBytes)
} else {
tempData = Data(count: bytesPerRow * planeHeight)
return tempData!.withUnsafeMutableBytes { bytes -> UnsafeRawPointer in
readDCTBlocks(Int32(planeWidth), Int32(planeHeight), planeData, bytes.baseAddress!.assumingMemoryBound(to: Float32.self), Int32(bytesPerRow / 4))
return UnsafeRawPointer(bytes.baseAddress!)
}
}
})
}
}
public func renderIdct(layer: MetalImageLayer, compressedImage: AnimationCompressor.CompressedImageData, completion: @escaping () -> Void) {
DispatchQueue.global().async {
self.updateIdctTextures(compressedImage: compressedImage)
DispatchQueue.main.async {
guard let compressedTextures = self.compressedTextures else {
return
}
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
return
}
commandBuffer.label = "MyCommand"
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else {
return
}
computeEncoder.setComputePipelineState(self.shared.computeIdctPipelineState)
let outputTextures: TextureSet
if let current = self.outputTextures, current.width == compressedTextures.textures[0].width, current.height == compressedTextures.textures[0].height {
outputTextures = current
} else {
guard let textures = TextureSet(
device: self.sharedContext.device,
width: compressedTextures.textures[0].width,
height: compressedTextures.textures[0].height,
descriptions: [
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r8Unorm
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r8Unorm
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .r8Unorm
),
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r8Unorm
)
],
usage: [.shaderRead, .shaderWrite],
isShared: false
) else {
return
}
self.outputTextures = textures
outputTextures = textures
}
for i in 0 ..< 4 {
computeEncoder.setTexture(compressedTextures.textures[i].texture, index: 0)
computeEncoder.setTexture(outputTextures.textures[i].texture, index: 1)
var colorPlaneInt32 = Int32(i)
computeEncoder.setBytes(&colorPlaneInt32, length: 4, index: 2)
let threadgroupSize = MTLSize(width: 8, height: 8, depth: 1)
let threadgroupCount = MTLSize(width: (compressedTextures.textures[i].width + threadgroupSize.width - 1) / threadgroupSize.width, height: (compressedTextures.textures[i].height + threadgroupSize.height - 1) / threadgroupSize.height, depth: 1)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}
computeEncoder.endEncoding()
let drawableSize = CGSize(width: CGFloat(outputTextures.textures[0].width), height: CGFloat(outputTextures.textures[0].height))
guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit()
completion()
return
}
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
return
}
renderEncoder.label = "MyRenderEncoder"
renderEncoder.setRenderPipelineState(self.shared.renderIdctPipelineState)
for i in 0 ..< 4 {
renderEncoder.setFragmentTexture(outputTextures.textures[i].texture, index: i)
}
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
renderEncoder.endEncoding()
var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
autoreleasepool {
storedDrawable?.present(completion: completion)
storedDrawable = nil
}
}
}
commandBuffer.commit()
}
}
}
private func updateRgbTexture(width: Int, height: Int, bytesPerRow: Int, data: Data) {
self.compressedTextures = nil
self.outputTextures = nil
self.yuvaTextures = nil
let rgbTexture: Texture
if let current = self.rgbTexture, current.width == width, current.height == height {
rgbTexture = current
} else {
guard let texture = Texture(device: self.sharedContext.device, width: width, height: height, pixelFormat: .bgra8Unorm, usage: .shaderRead, isShared: true) else {
return
}
self.rgbTexture = texture
rgbTexture = texture
}
rgbTexture.readDirect(width: width, height: height, bytesPerRow: bytesPerRow, read: { destinationBytes in
return data.withUnsafeBytes { bytes -> UnsafeRawPointer in
if let destinationBytes = destinationBytes {
memcpy(destinationBytes, bytes.baseAddress!, bytes.count)
return UnsafeRawPointer(destinationBytes)
} else {
return bytes.baseAddress!
}
}
})
}
public func renderRgb(layer: MetalImageLayer, width: Int, height: Int, bytesPerRow: Int, data: Data, completion: @escaping () -> Void) {
self.updateRgbTexture(width: width, height: height, bytesPerRow: bytesPerRow, data: data)
guard let rgbTexture = self.rgbTexture else {
return
}
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
return
}
commandBuffer.label = "MyCommand"
let drawableSize = CGSize(width: CGFloat(rgbTexture.width), height: CGFloat(rgbTexture.height))
guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit()
completion()
return
}
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
return
}
renderEncoder.label = "MyRenderEncoder"
renderEncoder.setRenderPipelineState(self.shared.renderRgbPipelineState)
renderEncoder.setFragmentTexture(rgbTexture.texture, index: 0)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
renderEncoder.endEncoding()
var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
autoreleasepool {
storedDrawable?.present(completion: completion)
storedDrawable = nil
}
}
}
commandBuffer.commit()
}
private func updateYuvaTextures(width: Int, height: Int, data: Data) {
if width % 2 != 0 || height % 2 != 0 {
return
}
self.compressedTextures = nil
self.outputTextures = nil
self.rgbTexture = nil
let yuvaTextures: TextureSet
if let current = self.yuvaTextures, current.width == width, current.height == height {
yuvaTextures = current
} else {
guard let textures = TextureSet(
device: self.sharedContext.device,
width: width,
height: height,
descriptions: [
TextureSet.Description(
fractionWidth: 1, fractionHeight: 1,
pixelFormat: .r8Unorm
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 2,
pixelFormat: .rg8Unorm
),
TextureSet.Description(
fractionWidth: 2, fractionHeight: 1,
pixelFormat: .r8Uint
)
],
usage: .shaderRead,
isShared: true
) else {
return
}
self.yuvaTextures = textures
yuvaTextures = textures
}
data.withUnsafeBytes { yuvaBuffer in
guard let yuva = yuvaBuffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
yuvaTextures.textures[0].readDirect(width: width, height: height, bytesPerRow: width, read: { destinationBytes in
if let destinationBytes = destinationBytes {
memcpy(destinationBytes, yuva.advanced(by: 0), width * height)
return UnsafeRawPointer(destinationBytes)
} else {
return UnsafeRawPointer(yuva.advanced(by: 0))
}
})
yuvaTextures.textures[1].readDirect(width: width / 2, height: height / 2, bytesPerRow: width, read: { destinationBytes in
if let destinationBytes = destinationBytes {
memcpy(destinationBytes, yuva.advanced(by: width * height), width * height / 2)
return UnsafeRawPointer(destinationBytes)
} else {
return UnsafeRawPointer(yuva.advanced(by: width * height))
}
})
yuvaTextures.textures[2].readDirect(width: width / 2, height: height, bytesPerRow: width / 2, read: { destinationBytes in
if let destinationBytes = destinationBytes {
memcpy(destinationBytes, yuva.advanced(by: width * height * 2), width / 2 * height)
return UnsafeRawPointer(destinationBytes)
} else {
return UnsafeRawPointer(yuva.advanced(by: width * height * 2))
}
})
}
}
public func renderYuva(layer: MetalImageLayer, width: Int, height: Int, data: Data, completion: @escaping () -> Void) {
DispatchQueue.global().async {
autoreleasepool {
//let renderStartTime = CFAbsoluteTimeGetCurrent()
var beginTime: Double = 0.0
var duration: Double = 0.0
beginTime = CFAbsoluteTimeGetCurrent()
self.updateYuvaTextures(width: width, height: height, data: data)
duration = CFAbsoluteTimeGetCurrent() - beginTime
if duration > 1.0 / 60.0 {
print("update textures lag \(duration * 1000.0)")
}
guard let yuvaTextures = self.yuvaTextures else {
DispatchQueue.main.async {
completion()
}
return
}
beginTime = CFAbsoluteTimeGetCurrent()
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
DispatchQueue.main.async {
completion()
}
return
}
commandBuffer.label = "MyCommand"
let drawableSize = CGSize(width: CGFloat(yuvaTextures.width), height: CGFloat(yuvaTextures.height))
guard let drawable = self.getNextDrawable(layer: layer, drawableSize: drawableSize) else {
commandBuffer.commit()
DispatchQueue.main.async {
completion()
}
return
}
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
DispatchQueue.main.async {
completion()
}
return
}
renderEncoder.label = "MyRenderEncoder"
renderEncoder.setRenderPipelineState(self.shared.renderYuvaPipelineState)
renderEncoder.setFragmentTexture(yuvaTextures.textures[0].texture, index: 0)
renderEncoder.setFragmentTexture(yuvaTextures.textures[1].texture, index: 1)
renderEncoder.setFragmentTexture(yuvaTextures.textures[2].texture, index: 2)
var alphaSize = simd_uint2(UInt32(yuvaTextures.textures[0].texture.width), UInt32(yuvaTextures.textures[0].texture.height))
renderEncoder.setFragmentBytes(&alphaSize, length: 8, index: 3)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
renderEncoder.endEncoding()
var storedDrawable: MetalImageLayer.Drawable? = drawable
commandBuffer.addCompletedHandler { _ in
DispatchQueue.main.async {
autoreleasepool {
storedDrawable?.present(completion: completion)
storedDrawable = nil
}
}
}
commandBuffer.commit()
duration = CFAbsoluteTimeGetCurrent() - beginTime
if duration > 1.0 / 60.0 {
print("commit lag \(duration * 1000.0)")
}
}
}
}
}