mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
426 lines
16 KiB
Swift
426 lines
16 KiB
Swift
import Foundation
|
|
import MetalKit
|
|
import LottieCpp
|
|
|
|
func evaluateBezier(p0: SIMD2<Float>, p1: SIMD2<Float>, p2: SIMD2<Float>, p3: SIMD2<Float>, t: Float) -> SIMD2<Float> {
|
|
let t2 = t * t
|
|
let t3 = t * t * t
|
|
|
|
let A = (3 * t2 - 3 * t3)
|
|
let B = (3 * t3 - 6 * t2 + 3 * t)
|
|
let C = (3 * t2 - t3 - 3 * t + 1)
|
|
|
|
let value = t3 * p3 + A * p2 + B * p1 + C * p0
|
|
return value
|
|
}
|
|
|
|
func evaluateBezier(p0: Float, p1: Float, p2: Float, p3: Float, t: Float) -> Float {
|
|
let oneMinusT = 1.0 - t
|
|
|
|
let value = oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3
|
|
return value
|
|
}
|
|
|
|
func solveQuadratic(p0: Float, p1: Float, p2: Float, p3: Float) -> (Float, Float) {
|
|
let i = p1 - p0
|
|
let j = p2 - p1
|
|
let k = p3 - p2
|
|
|
|
let a = (3 * i) - (6 * j) + (3 * k)
|
|
let b = (6 * j) - (6 * i)
|
|
let c = (3 * i)
|
|
|
|
let sqrtPart = (b * b) - (4 * a * c)
|
|
let hasSolution = sqrtPart >= 0
|
|
if !hasSolution {
|
|
return (.nan, .nan)
|
|
}
|
|
|
|
let t1 = (-b + sqrt(sqrtPart)) / (2 * a)
|
|
let t2 = (-b - sqrt(sqrtPart)) / (2 * a)
|
|
|
|
var s1: Float = .nan
|
|
var s2: Float = .nan
|
|
|
|
if t1 >= 0.0 && t1 <= 1.0 {
|
|
s1 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t1)
|
|
}
|
|
|
|
if t2 >= 0.0 && t2 <= 1.0 {
|
|
s2 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t2)
|
|
}
|
|
|
|
return (s1, s2)
|
|
}
|
|
|
|
func bezierBounds(p0: SIMD2<Float>, p1: SIMD2<Float>, p2: SIMD2<Float>, p3: SIMD2<Float>) -> (minPosition: SIMD2<Float>, maxPosition: SIMD2<Float>) {
|
|
let (solX1, solX2) = solveQuadratic(p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x)
|
|
let (solY1, solY2) = solveQuadratic(p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y)
|
|
|
|
var minX = min(p0.x, p3.x)
|
|
var maxX = max(p0.x, p3.x)
|
|
|
|
if !solX1.isNaN {
|
|
minX = min(minX, solX1)
|
|
maxX = max(maxX, solX1)
|
|
}
|
|
|
|
if !solX2.isNaN {
|
|
minX = min(minX, solX2)
|
|
maxX = max(maxX, solX2)
|
|
}
|
|
|
|
var minY = min(p0.y, p3.y)
|
|
var maxY = max(p0.y, p3.y)
|
|
|
|
if !solY1.isNaN {
|
|
minY = min(minY, solY1)
|
|
maxY = max(maxY, solY1)
|
|
}
|
|
|
|
if !solY2.isNaN {
|
|
minY = min(minY, solY2)
|
|
maxY = max(maxY, solY2)
|
|
}
|
|
|
|
return (SIMD2<Float>(minX, minY), SIMD2<Float>(maxX, maxY))
|
|
}
|
|
|
|
final class PathRenderSubpathStrokeState {
|
|
struct TerminalState {
|
|
var bufferOffset: Int
|
|
var segmentCount: Int
|
|
}
|
|
|
|
enum UnresolvedPosition {
|
|
case position(SIMD2<Float>)
|
|
case curve(p0: SIMD2<Float>, p1: SIMD2<Float>, p2: SIMD2<Float>, p3: SIMD2<Float>, t: Float)
|
|
|
|
func resolve() -> SIMD2<Float> {
|
|
switch self {
|
|
case let .position(value):
|
|
return value
|
|
case let .curve(p0, p1, p2, p3, t):
|
|
return evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t)
|
|
}
|
|
}
|
|
}
|
|
|
|
private let buffer: PathRenderBuffer
|
|
private let bezierDataBuffer: PathRenderBuffer
|
|
let bufferOffset: Int
|
|
private(set) var vertexCount: Int = 0
|
|
|
|
private(set) var terminalState: TerminalState?
|
|
|
|
private(set) var curveJoinVertexRanges: [Range<Int>] = []
|
|
|
|
private var firstPosition: SIMD2<Float>
|
|
private var secondPosition: UnresolvedPosition
|
|
private var thirdPosition: UnresolvedPosition
|
|
|
|
private var lastPosition: SIMD2<Float>
|
|
private var lastMinus1Position: UnresolvedPosition
|
|
private var lastMinus2Position: UnresolvedPosition
|
|
|
|
private(set) var isClosed: Bool = false
|
|
private(set) var isCompleted: Bool = false
|
|
|
|
init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2<Float>) {
|
|
self.buffer = buffer
|
|
self.bezierDataBuffer = bezierDataBuffer
|
|
self.bufferOffset = buffer.length
|
|
|
|
self.firstPosition = point
|
|
self.secondPosition = .position(point)
|
|
self.thirdPosition = .position(point)
|
|
self.lastPosition = point
|
|
self.lastMinus1Position = .position(point)
|
|
self.lastMinus2Position = .position(point)
|
|
|
|
self.add(point: point)
|
|
}
|
|
|
|
func add(point: SIMD2<Float>) {
|
|
self.buffer.append(float2: point)
|
|
|
|
self.lastMinus2Position = self.lastMinus1Position
|
|
self.lastMinus1Position = .position(self.lastPosition)
|
|
self.lastPosition = point
|
|
|
|
self.vertexCount += 1
|
|
if self.vertexCount == 2 {
|
|
self.secondPosition = .position(point)
|
|
} else if self.vertexCount == 3 {
|
|
self.thirdPosition = .position(point)
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
if self.vertexCount == 1 {
|
|
self.secondPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(1) / Float(stepCount))
|
|
self.thirdPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(2) / Float(stepCount))
|
|
}
|
|
|
|
self.vertexCount += stepCount
|
|
|
|
self.lastMinus2Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 2) / Float(stepCount))
|
|
self.lastMinus1Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 1) / Float(stepCount))
|
|
self.lastPosition = point
|
|
}
|
|
|
|
func close() {
|
|
if self.isClosed {
|
|
assert(false)
|
|
} else {
|
|
self.isClosed = true
|
|
|
|
if self.lastPosition != self.firstPosition {
|
|
self.add(point: self.firstPosition)
|
|
}
|
|
}
|
|
}
|
|
|
|
func complete() {
|
|
if self.isCompleted {
|
|
assert(false)
|
|
} else {
|
|
if self.isClosed {
|
|
if self.vertexCount >= 3 {
|
|
self.buffer.append(float2: self.secondPosition.resolve())
|
|
self.buffer.append(float2: self.thirdPosition.resolve())
|
|
self.vertexCount += 2
|
|
}
|
|
} else {
|
|
if self.vertexCount == 2 {
|
|
let terminalBufferOffset = self.buffer.length
|
|
|
|
let resolvedSecond = self.secondPosition.resolve()
|
|
self.buffer.append(float2: self.firstPosition)
|
|
self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5)
|
|
self.buffer.append(float2: resolvedSecond)
|
|
|
|
self.buffer.append(float2: resolvedSecond)
|
|
self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5)
|
|
self.buffer.append(float2: self.firstPosition)
|
|
|
|
self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2)
|
|
} else if self.vertexCount >= 3 {
|
|
let terminalBufferOffset = self.buffer.length
|
|
|
|
self.buffer.append(float2: self.firstPosition)
|
|
self.buffer.append(float2: self.secondPosition.resolve())
|
|
self.buffer.append(float2: self.thirdPosition.resolve())
|
|
|
|
self.buffer.append(float2: self.lastPosition)
|
|
self.buffer.append(float2: self.lastMinus1Position.resolve())
|
|
self.buffer.append(float2: self.lastMinus2Position.resolve())
|
|
|
|
self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final class PathRenderStrokeState {
|
|
private let buffer: PathRenderBuffer
|
|
private let bezierDataBuffer: PathRenderBuffer
|
|
private let lineWidth: Float
|
|
private let lineJoin: CGLineJoin
|
|
private let lineCap: CGLineCap
|
|
private let miterLimit: Float
|
|
private let color: LottieColor
|
|
private let transform: CATransform3D
|
|
|
|
private var currentSubpath: PathRenderSubpathStrokeState?
|
|
private(set) var subpaths: [PathRenderSubpathStrokeState] = []
|
|
|
|
init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, lineWidth: Float, lineJoin: CGLineJoin, lineCap: CGLineCap, miterLimit: Float, color: LottieColor, transform: CATransform3D) {
|
|
self.buffer = buffer
|
|
self.bezierDataBuffer = bezierDataBuffer
|
|
self.lineWidth = lineWidth
|
|
self.lineJoin = lineJoin
|
|
self.lineCap = lineCap
|
|
self.miterLimit = miterLimit
|
|
self.color = color
|
|
self.transform = transform
|
|
}
|
|
|
|
func begin(point: SIMD2<Float>) {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.complete()
|
|
self.subpaths.append(currentSubpath)
|
|
self.currentSubpath = nil
|
|
}
|
|
|
|
self.currentSubpath = PathRenderSubpathStrokeState(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()
|
|
currentSubpath.complete()
|
|
self.subpaths.append(currentSubpath)
|
|
self.currentSubpath = nil
|
|
}
|
|
}
|
|
|
|
func complete() {
|
|
if let currentSubpath = self.currentSubpath {
|
|
currentSubpath.complete()
|
|
self.subpaths.append(currentSubpath)
|
|
self.currentSubpath = nil
|
|
}
|
|
}
|
|
|
|
func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) {
|
|
if self.subpaths.isEmpty {
|
|
return
|
|
}
|
|
|
|
encoder.setVertexBuffer(buffer, offset: 0, index: 0)
|
|
|
|
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)
|
|
|
|
var transformMatrix = SIMD16<Float>(
|
|
Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14),
|
|
Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24),
|
|
Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34),
|
|
Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44)
|
|
)
|
|
encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1)
|
|
|
|
let capRes2: Float
|
|
switch self.lineCap {
|
|
case .butt:
|
|
capRes2 = 2.0
|
|
case .square:
|
|
capRes2 = 6.0
|
|
case .round:
|
|
capRes2 = 24.0
|
|
@unknown default:
|
|
capRes2 = 2.0
|
|
}
|
|
let joinRes2: Float = self.lineJoin == .round ? 16.0 : 2.0
|
|
|
|
func computeCount(isEndpoints: Bool, insertCaps: Bool) -> SIMD2<Float> {
|
|
if insertCaps {
|
|
if isEndpoints {
|
|
return SIMD2<Float>(capRes2, max(capRes2, joinRes2))
|
|
} else {
|
|
return SIMD2<Float>(max(capRes2, joinRes2), max(capRes2, joinRes2))
|
|
}
|
|
} else {
|
|
if isEndpoints {
|
|
return SIMD2<Float>(capRes2, joinRes2)
|
|
} else {
|
|
return SIMD2<Float>(joinRes2, joinRes2)
|
|
}
|
|
}
|
|
}
|
|
|
|
var hasTerminalStates = false
|
|
|
|
for subpath in self.subpaths {
|
|
let segmentCount = subpath.vertexCount - 1
|
|
if segmentCount <= 0 {
|
|
continue
|
|
}
|
|
|
|
if subpath.vertexCount >= 4 {
|
|
encoder.setRenderPipelineState(context.strokeInnerPipelineState)
|
|
|
|
encoder.setVertexBufferOffset(subpath.bufferOffset, index: 0)
|
|
|
|
var vertCnt2 = computeCount(isEndpoints: false, insertCaps: false)
|
|
encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2)
|
|
|
|
var capJoinRes2 = SIMD2<Float>(capRes2, joinRes2)
|
|
encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3)
|
|
|
|
var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0
|
|
encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4)
|
|
|
|
var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0
|
|
encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5)
|
|
|
|
var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0
|
|
encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6)
|
|
|
|
var lineWidthValue: Float = self.lineWidth * 0.5
|
|
encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7)
|
|
|
|
let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2
|
|
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: subpath.vertexCount - 4 + 1, baseInstance: 0)
|
|
}
|
|
|
|
if subpath.terminalState != nil {
|
|
hasTerminalStates = true
|
|
}
|
|
}
|
|
|
|
if hasTerminalStates {
|
|
encoder.setRenderPipelineState(context.strokeTerminalPipelineState)
|
|
|
|
for subpath in self.subpaths {
|
|
let segmentCount = subpath.vertexCount - 1
|
|
if segmentCount <= 0 {
|
|
continue
|
|
}
|
|
|
|
if !subpath.isClosed {
|
|
if let terminalState = subpath.terminalState {
|
|
encoder.setVertexBufferOffset(terminalState.bufferOffset, index: 0)
|
|
|
|
var vertCnt2 = computeCount(isEndpoints: true, insertCaps: false)
|
|
encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2)
|
|
|
|
var capJoinRes2 = SIMD2<Float>(capRes2, joinRes2)
|
|
encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3)
|
|
|
|
var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0
|
|
encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4)
|
|
|
|
var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0
|
|
encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5)
|
|
|
|
var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0
|
|
encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6)
|
|
|
|
var lineWidthValue: Float = self.lineWidth * 0.5
|
|
encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7)
|
|
|
|
let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2
|
|
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: terminalState.segmentCount, baseInstance: 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|