Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,375 @@
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))
}
}
*/
@@ -0,0 +1,77 @@
import Foundation
import MetalKit
import LottieCpp
final class PathRenderBuffer {
private(set) var memory: UnsafeMutableRawPointer
private(set) var capacity: Int = 8 * 1024 * 1024
private(set) var length: Int = 0
init() {
self.memory = malloc(self.capacity)!
}
func reset() {
self.length = 0
}
func append(bytes: UnsafeRawPointer, length: Int) {
assert(length % 4 == 0)
if self.length + length > self.capacity {
self.capacity = self.capacity * 2
preconditionFailure()
}
memcpy(self.memory.advanced(by: self.length), bytes, length)
self.length += length
}
func appendZero(count: Int) {
if self.length + length > self.capacity {
self.capacity = self.capacity * 2
preconditionFailure()
}
self.length += count
}
func append(float: Float) {
var value: Float = float
self.append(bytes: &value, length: 4)
}
func append(float2: SIMD2<Float>) {
var value: SIMD2<Float> = float2
self.append(bytes: &value, length: 4 * 2)
}
func append(float3: SIMD3<Float>) {
var value = float3.x
self.append(bytes: &value, length: 4)
value = float3.y
self.append(bytes: &value, length: 4)
value = float3.z
self.append(bytes: &value, length: 4)
}
func append(int: Int32) {
var value = int
self.append(bytes: &value, length: 4)
}
func appendBezierData(
bufferOffset: Int,
start: SIMD2<Float>,
end: SIMD2<Float>,
cp1: SIMD2<Float>,
cp2: SIMD2<Float>,
offset: Float
) {
self.append(int: Int32(bufferOffset))
self.append(float2: start)
self.append(float2: end)
self.append(float2: cp1)
self.append(float2: cp2)
self.append(float: offset)
}
}
@@ -0,0 +1,208 @@
import Foundation
import MetalKit
import LottieCpp
final class PathRenderContext {
let device: MTLDevice
let msaaSampleCount: Int
let prepareBezierPipelineState: MTLComputePipelineState
let shapePipelineState: MTLRenderPipelineState
let clearPipelineState: MTLRenderPipelineState
let mergeColorFillPipelineState: MTLRenderPipelineState
let mergeLinearGradientFillPipelineState: MTLRenderPipelineState
let mergeRadialGradientFillPipelineState: MTLRenderPipelineState
let strokeTerminalPipelineState: MTLRenderPipelineState
let strokeInnerPipelineState: MTLRenderPipelineState
let drawOffscreenPipelineState: MTLRenderPipelineState
let drawOffscreenWithMaskPipelineState: MTLRenderPipelineState
let maximumThreadGroupWidth: Int
init?(device: MTLDevice, msaaSampleCount: Int) {
self.device = device
self.msaaSampleCount = msaaSampleCount
self.maximumThreadGroupWidth = device.maxThreadsPerThreadgroup.width
guard let library = metalLibrary(device: device) else {
return nil
}
guard let quadVertexFunction = library.makeFunction(name: "quad_vertex_shader") else {
print("Unable to find vertex function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let shapeVertexFunction = library.makeFunction(name: "fill_vertex_shader") else {
print("Unable to find vertex function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let shapeFragmentFunction = library.makeFunction(name: "fragment_shader") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let clearFragmentFunction = library.makeFunction(name: "clear_mask_fragment") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let mergeColorFillFragmentFunction = library.makeFunction(name: "merge_color_fill_fragment_shader") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let mergeLinearGradientFillFragmentFunction = library.makeFunction(name: "merge_linear_gradient_fill_fragment_shader") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let mergeRadialGradientFillFragmentFunction = library.makeFunction(name: "merge_radial_gradient_fill_fragment_shader") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let strokeFragmentFunction = library.makeFunction(name: "stroke_fragment_shader") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let strokeTerminalVertexFunction = library.makeFunction(name: "strokeTerminalVertex") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let strokeInnerVertexFunction = library.makeFunction(name: "strokeInnerVertex") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let prepareBezierPipelineFunction = library.makeFunction(name: "evaluateBezier") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let quadOffscreenFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
guard let quadOffscreenWithMaskFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment_with_mask") else {
print("Unable to find fragment function. Are you sure you defined it and spelled the name right?")
return nil
}
self.prepareBezierPipelineState = try! device.makeComputePipelineState(function: prepareBezierPipelineFunction)
let shapePipelineDescriptor = MTLRenderPipelineDescriptor()
shapePipelineDescriptor.vertexFunction = shapeVertexFunction
shapePipelineDescriptor.fragmentFunction = shapeFragmentFunction
shapePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
shapePipelineDescriptor.colorAttachments[0].writeMask = []
shapePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
shapePipelineDescriptor.colorAttachments[1].writeMask = [.all]
shapePipelineDescriptor.rasterSampleCount = msaaSampleCount
guard let shapePipelineState = try? device.makeRenderPipelineState(descriptor: shapePipelineDescriptor) else {
preconditionFailure()
}
self.shapePipelineState = shapePipelineState
let clearPipelineDescriptor = MTLRenderPipelineDescriptor()
clearPipelineDescriptor.vertexFunction = quadVertexFunction
clearPipelineDescriptor.fragmentFunction = clearFragmentFunction
clearPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
clearPipelineDescriptor.colorAttachments[0].writeMask = []
clearPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
clearPipelineDescriptor.colorAttachments[1].writeMask = .all
clearPipelineDescriptor.rasterSampleCount = msaaSampleCount
guard let clearPipelineState = try? device.makeRenderPipelineState(descriptor: clearPipelineDescriptor) else {
preconditionFailure()
}
self.clearPipelineState = clearPipelineState
let mergePipelineDescriptor = MTLRenderPipelineDescriptor()
mergePipelineDescriptor.vertexFunction = quadVertexFunction
mergePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
mergePipelineDescriptor.colorAttachments[0].writeMask = [.all]
mergePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add
mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add
mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one
mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one
mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .one
mergePipelineDescriptor.rasterSampleCount = msaaSampleCount
mergePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
mergePipelineDescriptor.colorAttachments[1].writeMask = []
mergePipelineDescriptor.fragmentFunction = mergeColorFillFragmentFunction
guard let mergeColorFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else {
preconditionFailure()
}
self.mergeColorFillPipelineState = mergeColorFillPipelineState
mergePipelineDescriptor.fragmentFunction = mergeLinearGradientFillFragmentFunction
guard let mergeLinearGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else {
preconditionFailure()
}
self.mergeLinearGradientFillPipelineState = mergeLinearGradientFillPipelineState
mergePipelineDescriptor.fragmentFunction = mergeRadialGradientFillFragmentFunction
guard let mergeRadialGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else {
preconditionFailure()
}
self.mergeRadialGradientFillPipelineState = mergeRadialGradientFillPipelineState
let strokePipelineDescriptor = MTLRenderPipelineDescriptor()
strokePipelineDescriptor.fragmentFunction = strokeFragmentFunction
strokePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
strokePipelineDescriptor.colorAttachments[0].writeMask = [.all]
strokePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
strokePipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation
strokePipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation
strokePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor
strokePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor
strokePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor
strokePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor
strokePipelineDescriptor.rasterSampleCount = msaaSampleCount
strokePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
strokePipelineDescriptor.colorAttachments[1].writeMask = []
strokePipelineDescriptor.vertexFunction = strokeTerminalVertexFunction
guard let strokeTerminalPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else {
preconditionFailure()
}
self.strokeTerminalPipelineState = strokeTerminalPipelineState
strokePipelineDescriptor.vertexFunction = strokeInnerVertexFunction
guard let strokeInnerPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else {
preconditionFailure()
}
self.strokeInnerPipelineState = strokeInnerPipelineState
let drawOffscreenPipelineDescriptor = MTLRenderPipelineDescriptor()
drawOffscreenPipelineDescriptor.vertexFunction = quadVertexFunction
drawOffscreenPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
drawOffscreenPipelineDescriptor.colorAttachments[0].writeMask = [.all]
drawOffscreenPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
drawOffscreenPipelineDescriptor.colorAttachments[1].writeMask = []
drawOffscreenPipelineDescriptor.rasterSampleCount = msaaSampleCount
drawOffscreenPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
drawOffscreenPipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation
drawOffscreenPipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation
drawOffscreenPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor
drawOffscreenPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor
drawOffscreenPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor
drawOffscreenPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor
drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenFragmentFunction
guard let drawOffscreenPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else {
preconditionFailure()
}
self.drawOffscreenPipelineState = drawOffscreenPipelineState
drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenWithMaskFragmentFunction
guard let drawOffscreenWithMaskPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else {
preconditionFailure()
}
self.drawOffscreenWithMaskPipelineState = drawOffscreenWithMaskPipelineState
}
}
@@ -0,0 +1,277 @@
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)
}
}
*/
@@ -0,0 +1,425 @@
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)
}
}
}
}
}
}*/
@@ -0,0 +1,3 @@
import Foundation
import LottieCpp