mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
376 lines
17 KiB
Swift
376 lines
17 KiB
Swift
import Foundation
|
|
import MetalKit
|
|
import LottieCpp
|
|
|
|
/*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 PathFrameState {
|
|
struct RenderItem {
|
|
enum Content {
|
|
case fill(PathRenderFillState)
|
|
case stroke(PathRenderStrokeState)
|
|
case offscreen(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?)
|
|
|
|
func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer, canvasSize: CGSize) {
|
|
switch self {
|
|
case let .fill(fill):
|
|
fill.encode(context: context, encoder: encoder, buffer: buffer)
|
|
case let .stroke(stroke):
|
|
stroke.encode(context: context, encoder: encoder, buffer: buffer)
|
|
case let .offscreen(surface, rect, transform, opacity, mask):
|
|
surface.encode(context: context, encoder: encoder, canvasSize: canvasSize, rect: rect, transform: transform, opacity: opacity, mask: mask)
|
|
}
|
|
}
|
|
}
|
|
|
|
let content: Content
|
|
|
|
init(content: Content) {
|
|
self.content = content
|
|
}
|
|
}
|
|
|
|
final class MaskSurface {
|
|
enum Mode {
|
|
case regular
|
|
case inverse
|
|
}
|
|
|
|
let surface: Surface
|
|
let mode: Mode
|
|
|
|
init(surface: Surface, mode: Mode) {
|
|
self.surface = surface
|
|
self.mode = mode
|
|
}
|
|
}
|
|
|
|
final class Surface {
|
|
let width: Int
|
|
let height: Int
|
|
private let msaaSampleCount: Int
|
|
|
|
private var texture: MTLTexture?
|
|
|
|
private(set) var items: [RenderItem] = []
|
|
|
|
init(width: Int, height: Int, msaaSampleCount: Int) {
|
|
self.width = width
|
|
self.height = height
|
|
self.msaaSampleCount = msaaSampleCount
|
|
}
|
|
|
|
func add(fill: PathRenderFillState) {
|
|
self.items.append(RenderItem(content: .fill(fill)))
|
|
}
|
|
|
|
func add(stroke: PathRenderStrokeState) {
|
|
self.items.append(RenderItem(content: .stroke(stroke)))
|
|
}
|
|
|
|
func add(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) {
|
|
self.items.append(RenderItem(content: .offscreen(surface: surface, rect: rect, transform: transform, opacity: opacity, mask: mask)))
|
|
}
|
|
|
|
func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) {
|
|
guard let texture = self.texture else {
|
|
print("Trying to encode offscreen blit pass, but no texture is present")
|
|
return
|
|
}
|
|
if mask != nil {
|
|
encoder.setRenderPipelineState(context.drawOffscreenWithMaskPipelineState)
|
|
} else {
|
|
encoder.setRenderPipelineState(context.drawOffscreenPipelineState)
|
|
}
|
|
|
|
let identityTransform = CATransform3DIdentity
|
|
var identityTransformMatrix = SIMD16<Float>(
|
|
Float(identityTransform.m11), Float(identityTransform.m12), Float(identityTransform.m13), Float(identityTransform.m14),
|
|
Float(identityTransform.m21), Float(identityTransform.m22), Float(identityTransform.m23), Float(identityTransform.m24),
|
|
Float(identityTransform.m31), Float(identityTransform.m32), Float(identityTransform.m33), Float(identityTransform.m34),
|
|
Float(identityTransform.m41), Float(identityTransform.m42), Float(identityTransform.m43), Float(identityTransform.m44)
|
|
)
|
|
|
|
let boundingBox = rect.applying(CATransform3DGetAffineTransform(transform))
|
|
|
|
var quadVertices: [SIMD4<Float>] = [
|
|
SIMD4<Float>(Float(boundingBox.minX), Float(boundingBox.minY), 0.0, 0.0),
|
|
SIMD4<Float>(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0),
|
|
SIMD4<Float>(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0),
|
|
|
|
SIMD4<Float>(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0),
|
|
SIMD4<Float>(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0),
|
|
SIMD4<Float>(Float(boundingBox.maxX), Float(boundingBox.maxY), 1.0, 1.0)
|
|
]
|
|
|
|
encoder.setVertexBytes(&quadVertices, length: MemoryLayout<SIMD4<Float>>.size * quadVertices.count, index: 0)
|
|
encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1)
|
|
encoder.setFragmentTexture(texture, index: 0)
|
|
if let mask {
|
|
guard let maskTexture = mask.surface.texture else {
|
|
print("Trying to encode offscreen blit pass, but no mask texture is present")
|
|
return
|
|
}
|
|
encoder.setFragmentTexture(maskTexture, index: 1)
|
|
}
|
|
var opacity = opacity
|
|
encoder.setFragmentBytes(&opacity, length: 4, index: 1)
|
|
|
|
if let mask {
|
|
var maskMode: UInt32 = mask.mode == .regular ? 0 : 1;
|
|
encoder.setFragmentBytes(&maskMode, length: 4, index: 2)
|
|
}
|
|
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count)
|
|
}
|
|
|
|
func offscreenTextureDescriptor() -> MTLTextureDescriptor {
|
|
let textureDescriptor = MTLTextureDescriptor()
|
|
textureDescriptor.textureType = .type2D
|
|
textureDescriptor.width = self.width
|
|
textureDescriptor.height = self.height
|
|
textureDescriptor.pixelFormat = .bgra8Unorm
|
|
textureDescriptor.storageMode = .private
|
|
textureDescriptor.usage = [.renderTarget, .shaderRead]
|
|
return textureDescriptor
|
|
}
|
|
|
|
func offscreenTempTextureDescriptor() -> MTLTextureDescriptor {
|
|
let tempTextureDescriptor = MTLTextureDescriptor()
|
|
tempTextureDescriptor.sampleCount = self.msaaSampleCount
|
|
if self.msaaSampleCount == 1 {
|
|
tempTextureDescriptor.textureType = .type2D
|
|
} else {
|
|
tempTextureDescriptor.textureType = .type2DMultisample
|
|
}
|
|
tempTextureDescriptor.width = self.width
|
|
tempTextureDescriptor.height = self.height
|
|
tempTextureDescriptor.pixelFormat = .bgra8Unorm
|
|
tempTextureDescriptor.storageMode = .private
|
|
tempTextureDescriptor.usage = [.renderTarget, .shaderRead]
|
|
return tempTextureDescriptor
|
|
}
|
|
|
|
func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int {
|
|
var result = 0
|
|
|
|
var sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTextureDescriptor())
|
|
result += sizeAndAlign.size
|
|
|
|
sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTempTextureDescriptor())
|
|
result += sizeAndAlign.size * 2
|
|
|
|
for item in self.items {
|
|
if case let .offscreen(surface, _, _, _, mask) = item.content {
|
|
result += surface.calculateOffscreenHeapMemorySize(device: device)
|
|
if let mask {
|
|
result += mask.surface.calculateOffscreenHeapMemorySize(device: device)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, materializedBuffer: MTLBuffer, canvasSize: CGSize) {
|
|
guard let resultTexture = heap.makeTexture(descriptor: self.offscreenTextureDescriptor()) else {
|
|
return
|
|
}
|
|
|
|
for item in self.items {
|
|
if case let .offscreen(surface, _, _, _, mask) = item.content {
|
|
if let mask {
|
|
mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
}
|
|
|
|
self.texture = resultTexture
|
|
|
|
guard let offscreenTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else {
|
|
return
|
|
}
|
|
guard let tempTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else {
|
|
return
|
|
}
|
|
|
|
let offscreenRenderPassDescriptor = MTLRenderPassDescriptor()
|
|
if msaaSampleCount == 1 {
|
|
offscreenRenderPassDescriptor.colorAttachments[0].texture = resultTexture
|
|
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .store
|
|
} else {
|
|
offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture
|
|
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve
|
|
offscreenRenderPassDescriptor.colorAttachments[0].resolveTexture = resultTexture
|
|
}
|
|
offscreenRenderPassDescriptor.colorAttachments[0].loadAction = .clear
|
|
offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
|
|
|
|
offscreenRenderPassDescriptor.colorAttachments[1].texture = tempTexture
|
|
offscreenRenderPassDescriptor.colorAttachments[1].loadAction = .clear
|
|
offscreenRenderPassDescriptor.colorAttachments[1].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
|
|
offscreenRenderPassDescriptor.colorAttachments[1].storeAction = .dontCare
|
|
|
|
if self.msaaSampleCount == 4 {
|
|
offscreenRenderPassDescriptor.setSamplePositions([
|
|
MTLSamplePosition(x: 0.25, y: 0.25),
|
|
MTLSamplePosition(x: 0.75, y: 0.25),
|
|
MTLSamplePosition(x: 0.75, y: 0.75),
|
|
MTLSamplePosition(x: 0.25, y: 0.75)
|
|
])
|
|
}
|
|
|
|
guard let offscreenRenderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: offscreenRenderPassDescriptor) else {
|
|
return
|
|
}
|
|
|
|
for item in self.items {
|
|
item.content.encode(context: context, encoder: offscreenRenderEncoder, buffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
|
|
offscreenRenderEncoder.endEncoding()
|
|
}
|
|
}
|
|
|
|
let msaaSampleCount: Int
|
|
let buffer: PathRenderBuffer
|
|
let bezierDataBuffer: PathRenderBuffer
|
|
|
|
private var surfaceStack: [Surface] = []
|
|
|
|
private var materializedBuffer: MTLBuffer?
|
|
private var materializedBezierIndexBuffer: MTLBuffer?
|
|
|
|
init(width: Int, height: Int, msaaSampleCount: Int, buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer) {
|
|
self.msaaSampleCount = msaaSampleCount
|
|
self.buffer = buffer
|
|
self.bezierDataBuffer = bezierDataBuffer
|
|
self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: msaaSampleCount))
|
|
}
|
|
|
|
func pushOffscreen(width: Int, height: Int) {
|
|
self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: self.msaaSampleCount))
|
|
}
|
|
|
|
func popOffscreen(rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface? = nil) {
|
|
self.surfaceStack[self.surfaceStack.count - 2].add(surface: self.surfaceStack[self.surfaceStack.count - 1], rect: rect, transform: transform, opacity: opacity, mask: mask)
|
|
self.surfaceStack.removeLast()
|
|
}
|
|
|
|
func popOffscreenMask(mode: MaskSurface.Mode) -> MaskSurface {
|
|
return MaskSurface(
|
|
surface: self.surfaceStack.removeLast(),
|
|
mode: mode
|
|
)
|
|
}
|
|
|
|
func add(fill: PathRenderFillState) {
|
|
self.surfaceStack.last!.add(fill: fill)
|
|
}
|
|
|
|
func add(stroke: PathRenderStrokeState) {
|
|
self.surfaceStack.last!.add(stroke: stroke)
|
|
}
|
|
|
|
func prepare(heap: MTLHeap) {
|
|
if self.buffer.length == 0 {
|
|
return
|
|
}
|
|
|
|
var bufferOptions: MTLResourceOptions = [.storageModeShared, .cpuCacheModeWriteCombined]
|
|
if #available(iOS 13.0, *) {
|
|
bufferOptions.insert(.hazardTrackingModeTracked)
|
|
}
|
|
|
|
guard let materializedBuffer = heap.makeBuffer(length: self.buffer.length, options: bufferOptions) else {
|
|
print("Could not create materialized buffer")
|
|
return
|
|
}
|
|
materializedBuffer.label = "materializedBuffer"
|
|
self.materializedBuffer = materializedBuffer
|
|
|
|
memcpy(materializedBuffer.contents(), self.buffer.memory, self.buffer.length)
|
|
|
|
if self.bezierDataBuffer.length != 0 {
|
|
guard let materializedBezierIndexBuffer = heap.makeBuffer(length: self.bezierDataBuffer.length, options: bufferOptions) else {
|
|
print("Could not create materialized bezier index buffer")
|
|
return
|
|
}
|
|
self.materializedBezierIndexBuffer = materializedBezierIndexBuffer
|
|
materializedBezierIndexBuffer.label = "materializedBezierIndexBuffer"
|
|
|
|
memcpy(materializedBezierIndexBuffer.contents(), self.bezierDataBuffer.memory, self.bezierDataBuffer.length)
|
|
}
|
|
}
|
|
|
|
func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int {
|
|
var result = 0
|
|
for item in self.surfaceStack[0].items {
|
|
if case let .offscreen(surface, _, _, _, mask) = item.content {
|
|
result += surface.calculateOffscreenHeapMemorySize(device: device)
|
|
if let mask {
|
|
result += mask.surface.calculateOffscreenHeapMemorySize(device: device)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, canvasSize: CGSize) {
|
|
guard let materializedBuffer = self.materializedBuffer else {
|
|
return
|
|
}
|
|
|
|
assert(self.surfaceStack.count == 1)
|
|
|
|
for item in self.surfaceStack[0].items {
|
|
if case let .offscreen(surface, _, _, _, mask) = item.content {
|
|
if let mask {
|
|
mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
}
|
|
}
|
|
|
|
func encodeRender(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize) {
|
|
guard let materializedBuffer = self.materializedBuffer else {
|
|
return
|
|
}
|
|
|
|
assert(self.surfaceStack.count == 1)
|
|
|
|
for item in self.surfaceStack[0].items {
|
|
item.content.encode(context: context, encoder: encoder, buffer: materializedBuffer, canvasSize: canvasSize)
|
|
}
|
|
}
|
|
|
|
func encodeCompute(context: PathRenderContext, computeEncoder: MTLComputeCommandEncoder) {
|
|
guard let materializedBuffer = self.materializedBuffer, let materializedBezierIndexBuffer = self.materializedBezierIndexBuffer else {
|
|
return
|
|
}
|
|
|
|
let itemSize = 4 + 4 * 4 * 2 + 4
|
|
let itemCount = self.bezierDataBuffer.length / itemSize
|
|
|
|
computeEncoder.setComputePipelineState(context.prepareBezierPipelineState)
|
|
|
|
let threadGroupWidth = 16
|
|
let threadGroupHeight = 8
|
|
|
|
computeEncoder.useResource(materializedBuffer, usage: .write)
|
|
|
|
computeEncoder.setBuffer(materializedBezierIndexBuffer, offset: 0, index: 0)
|
|
computeEncoder.setBuffer(materializedBuffer, offset: 0, index: 1)
|
|
var itemCountSize: UInt32 = UInt32(itemCount)
|
|
computeEncoder.setBytes(&itemCountSize, length: 4, index: 2)
|
|
let dispatchSize = alignUp(size: itemCount, align: threadGroupWidth)
|
|
computeEncoder.dispatchThreadgroups(MTLSize(width: dispatchSize, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: threadGroupHeight, depth: 1))
|
|
}
|
|
}
|
|
*/
|