mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
278 lines
11 KiB
Swift
278 lines
11 KiB
Swift
import Foundation
|
|
import MetalKit
|
|
import simd
|
|
import LottieCpp
|
|
|
|
enum PathShading {
|
|
final class Gradient {
|
|
enum GradientType {
|
|
case linear
|
|
case radial
|
|
}
|
|
|
|
struct ColorStop {
|
|
var color: LottieColor
|
|
var location: Float
|
|
|
|
init(color: LottieColor, location: Float) {
|
|
self.color = color
|
|
self.location = location
|
|
}
|
|
}
|
|
|
|
let gradientType: GradientType
|
|
let colorStops: [ColorStop]
|
|
let start: SIMD2<Float>
|
|
let end: SIMD2<Float>
|
|
|
|
init(gradientType: GradientType, colorStops: [ColorStop], start: SIMD2<Float>, end: SIMD2<Float>) {
|
|
self.gradientType = gradientType
|
|
self.colorStops = colorStops
|
|
self.start = start
|
|
self.end = end
|
|
}
|
|
}
|
|
|
|
case color(LottieColor)
|
|
case gradient(Gradient)
|
|
}
|
|
|
|
final class PathRenderSubpathFillState {
|
|
private let buffer: PathRenderBuffer
|
|
private let bezierDataBuffer: PathRenderBuffer
|
|
let bufferOffset: Int
|
|
private(set) var vertexCount: Int = 0
|
|
|
|
private var firstPosition: SIMD2<Float>
|
|
private var lastPosition: SIMD2<Float>
|
|
|
|
private(set) var minPosition: SIMD2<Float>
|
|
private(set) var maxPosition: SIMD2<Float>
|
|
|
|
private var isClosed: Bool = false
|
|
|
|
init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2<Float>) {
|
|
self.buffer = buffer
|
|
self.bezierDataBuffer = bezierDataBuffer
|
|
self.bufferOffset = buffer.length
|
|
|
|
self.firstPosition = point
|
|
self.lastPosition = point
|
|
self.minPosition = point
|
|
self.maxPosition = point
|
|
|
|
self.add(point: point)
|
|
}
|
|
|
|
func add(point: SIMD2<Float>) {
|
|
self.buffer.append(float2: point)
|
|
|
|
self.minPosition.x = min(self.minPosition.x, point.x)
|
|
self.minPosition.y = min(self.minPosition.y, point.y)
|
|
self.maxPosition.x = max(self.maxPosition.x, point.x)
|
|
self.maxPosition.y = max(self.maxPosition.y, point.y)
|
|
|
|
self.lastPosition = point
|
|
|
|
self.vertexCount += 1
|
|
}
|
|
|
|
func addCurve(to point: SIMD2<Float>, cp1: SIMD2<Float>, cp2: SIMD2<Float>) {
|
|
let stepCount = 8
|
|
self.bezierDataBuffer.appendBezierData(
|
|
bufferOffset: self.buffer.length / 4,
|
|
start: self.lastPosition,
|
|
end: point,
|
|
cp1: cp1,
|
|
cp2: cp2,
|
|
offset: 0.0
|
|
)
|
|
self.buffer.appendZero(count: 4 * 2 * stepCount)
|
|
self.vertexCount += stepCount
|
|
|
|
let (curveMin, curveMax) = bezierBounds(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point)
|
|
|
|
self.minPosition.x = min(self.minPosition.x, curveMin.x)
|
|
self.minPosition.y = min(self.minPosition.y, curveMin.y)
|
|
self.maxPosition.x = max(self.maxPosition.x, curveMax.x)
|
|
self.maxPosition.y = max(self.maxPosition.y, curveMax.y)
|
|
|
|
self.lastPosition = point
|
|
}
|
|
|
|
func close() {
|
|
if self.isClosed {
|
|
assert(false)
|
|
} else {
|
|
self.isClosed = true
|
|
|
|
if self.lastPosition != self.firstPosition {
|
|
self.add(point: self.firstPosition)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final class PathRenderFillState {
|
|
private let buffer: PathRenderBuffer
|
|
private let bezierDataBuffer: PathRenderBuffer
|
|
private let fillRule: LottieFillRule
|
|
private let shading: PathShading
|
|
private let transform: CATransform3D
|
|
|
|
private var currentSubpath: PathRenderSubpathFillState?
|
|
private(set) var subpaths: [PathRenderSubpathFillState] = []
|
|
|
|
init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, fillRule: LottieFillRule, shading: PathShading, transform: CATransform3D) {
|
|
self.buffer = buffer
|
|
self.bezierDataBuffer = bezierDataBuffer
|
|
self.fillRule = fillRule
|
|
self.shading = shading
|
|
self.transform = transform
|
|
}
|
|
|
|
func begin(point: SIMD2<Float>) {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.close()
|
|
self.subpaths.append(currentSubpath)
|
|
self.currentSubpath = nil
|
|
}
|
|
|
|
self.currentSubpath = PathRenderSubpathFillState(buffer: self.buffer, bezierDataBuffer: self.bezierDataBuffer, point: point)
|
|
}
|
|
|
|
func addLine(to point: SIMD2<Float>) {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.add(point: point)
|
|
}
|
|
}
|
|
|
|
func addCurve(to point: SIMD2<Float>, cp1: SIMD2<Float>, cp2: SIMD2<Float>) {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.addCurve(to: point, cp1: cp1, cp2: cp2)
|
|
}
|
|
}
|
|
|
|
func close() {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.close()
|
|
self.subpaths.append(currentSubpath)
|
|
self.currentSubpath = nil
|
|
}
|
|
}
|
|
|
|
func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) {
|
|
if self.subpaths.isEmpty {
|
|
return
|
|
}
|
|
var minPosition: SIMD2<Float> = self.subpaths[0].minPosition
|
|
var maxPosition: SIMD2<Float> = self.subpaths[0].maxPosition
|
|
for subpath in self.subpaths {
|
|
minPosition.x = min(minPosition.x, subpath.minPosition.x)
|
|
minPosition.y = min(minPosition.y, subpath.minPosition.y)
|
|
maxPosition.x = max(maxPosition.x, subpath.maxPosition.x)
|
|
maxPosition.y = max(maxPosition.y, subpath.maxPosition.y)
|
|
}
|
|
|
|
let localBoundingBox = CGRect(x: CGFloat(minPosition.x), y: CGFloat(minPosition.y), width: CGFloat(maxPosition.x - minPosition.x), height: CGFloat(maxPosition.y - minPosition.y))
|
|
if localBoundingBox.isEmpty {
|
|
return
|
|
}
|
|
|
|
var transformMatrix = simd_float4x4(
|
|
SIMD4<Float>(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14)),
|
|
SIMD4<Float>(Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24)),
|
|
SIMD4<Float>(Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34)),
|
|
SIMD4<Float>(Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44))
|
|
)
|
|
|
|
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 transform = CATransform3DGetAffineTransform(self.transform)
|
|
let boundingBox = localBoundingBox.applying(transform)
|
|
let baseVertex = boundingBox.origin.applying(transform.inverted())
|
|
|
|
encoder.setRenderPipelineState(context.clearPipelineState)
|
|
|
|
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.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count)
|
|
|
|
encoder.setRenderPipelineState(context.shapePipelineState)
|
|
encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1)
|
|
var baseVertexData = SIMD2<Float>(Float(baseVertex.x), Float(baseVertex.y))
|
|
encoder.setVertexBytes(&baseVertexData, length: 4 * 2, index: 2)
|
|
|
|
var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1
|
|
encoder.setFragmentBytes(&modeBytes, length: 4, index: 1)
|
|
|
|
for subpath in self.subpaths {
|
|
encoder.setVertexBuffer(buffer, offset: subpath.bufferOffset, index: 0)
|
|
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: (subpath.vertexCount - 1) * 3)
|
|
}
|
|
|
|
encoder.setVertexBytes(&quadVertices, length: MemoryLayout<SIMD4<Float>>.size * quadVertices.count, index: 0)
|
|
encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1)
|
|
|
|
switch self.shading {
|
|
case let .color(color):
|
|
encoder.setRenderPipelineState(context.mergeColorFillPipelineState)
|
|
|
|
var colorVector = SIMD4(Float(color.r), Float(color.g), Float(color.b), Float(color.a))
|
|
encoder.setFragmentBytes(&colorVector, length: MemoryLayout<SIMD4<Float>>.size, index: 0)
|
|
case let .gradient(gradient):
|
|
switch gradient.gradientType {
|
|
case .linear:
|
|
encoder.setRenderPipelineState(context.mergeLinearGradientFillPipelineState)
|
|
case .radial:
|
|
encoder.setRenderPipelineState(context.mergeRadialGradientFillPipelineState)
|
|
}
|
|
|
|
var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1
|
|
encoder.setFragmentBytes(&modeBytes, length: 4, index: 1)
|
|
|
|
let colorStopSize = 4 * 4 + 4
|
|
var colorStopsData = Data(count: colorStopSize * gradient.colorStops.count)
|
|
colorStopsData.withUnsafeMutableBytes { buffer in
|
|
let bytes = buffer.baseAddress!.assumingMemoryBound(to: Float.self)
|
|
for i in 0 ..< gradient.colorStops.count {
|
|
let colorStop = gradient.colorStops[i]
|
|
bytes[i * 5 + 0] = Float(colorStop.color.r)
|
|
bytes[i * 5 + 1] = Float(colorStop.color.g)
|
|
bytes[i * 5 + 2] = Float(colorStop.color.b)
|
|
bytes[i * 5 + 3] = Float(colorStop.color.a)
|
|
bytes[i * 5 + 4] = colorStop.location
|
|
}
|
|
encoder.setFragmentBytes(buffer.baseAddress!, length: buffer.count, index: 0)
|
|
}
|
|
|
|
var numColorStops: UInt32 = UInt32(gradient.colorStops.count)
|
|
encoder.setFragmentBytes(&numColorStops, length: 4, index: 2)
|
|
|
|
var startPosition = transformMatrix * SIMD4<Float>(gradient.start.x, gradient.start.y, 0.0, 1.0)
|
|
encoder.setFragmentBytes(&startPosition, length: 4 * 2, index: 3)
|
|
var endPosition = transformMatrix * SIMD4<Float>(gradient.end.x, gradient.end.y, 0.0, 1.0)
|
|
encoder.setFragmentBytes(&endPosition, length: 4 * 2, index: 4)
|
|
}
|
|
|
|
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count)
|
|
}
|
|
}
|
|
|