├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ └── MeshGradient.xcscheme
├── CHANGELOG.md
├── Files
└── mesh.gif
├── LICENSE
├── Package.swift
├── README.md
└── Sources
├── MeshGradient
├── ControlPoint.swift
├── DummyResources
│ └── please_swiftpm
├── Grid.swift
├── MTLBufferPool.swift
├── MTLComputeHermitPatchMatrix.metal
├── MTLComputeHermitPatchMatrixFunction.swift
├── MTLComputeMeshTrianglePrimitives.metal
├── MTLComputeMeshTrianglePrimitivesFunction.swift
├── MTLComputeNoise.metal
├── MTLComputeNoiseFunction.swift
├── MTLComputeShuffleCoefficients.metal
├── MTLComputeShuffleCoefficientsFunction.swift
├── MTLDrawMeshTriangles.metal
├── MTLDrawMeshTrianglesFunction.swift
├── MeshAnimator.swift
├── MeshDataProvider.swift
├── MeshGenerator.swift
├── MeshGradientError.swift
├── MeshRandomizer.swift
├── MetalMeshRenderer.swift
└── SwiftUI
│ └── LegacyMeshGradient.swift
└── MeshGradientCHeaders
├── PleasingSwiftPM.c
└── include
└── MetalMeshShaderTypes.h
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/MeshGradient.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.0.2
4 |
5 | * Reflect `framesPerSecond` change in `MeshGradient` SwiftUI view.
6 | [Pull request](https://github.com/Nekitosss/MeshGradient/pull/1)
7 |
8 | ## 1.0.1
9 |
10 | * Enable non-uniform mesh gradient dimensions. E.g. 4x8 or 5x12 meshes now possible
11 | * Fix `MeshGradient` view name for macOS
12 |
--------------------------------------------------------------------------------
/Files/mesh.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patskovn/MeshGradient/f16b544336b7bdaf7e3eba4ac6cba50e1160220e/Files/mesh.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022 Nikita Patskov and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.6
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "MeshGradient",
7 | platforms: [
8 | .macOS(.v10_15),
9 | .iOS(.v13),
10 | .macCatalyst(.v13),
11 | .tvOS(.v13),
12 | ],
13 | products: [
14 | .library(
15 | name: "MeshGradient",
16 | targets: ["MeshGradient"]),
17 | .library(name: "MeshGradientCHeaders",
18 | targets: ["MeshGradientCHeaders"]),
19 | ],
20 | targets: [
21 | .target(
22 | name: "MeshGradient",
23 | dependencies: ["MeshGradientCHeaders"],
24 | resources: [.copy("DummyResources/")]
25 | ),
26 | .target(name: "MeshGradientCHeaders"),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MeshGradient
2 |
3 | Metal-based implementation of beautiful mesh gradient. Image worth thousands of words, so just let me show you.
4 |
5 | 
6 |
7 | ## How to generate mesh
8 |
9 | Small notes before I show you the code. This mesh is generated from the constants you provide. To create mesh you have to provide grid with `ControlPoint` matrix that *describe* your gradient (just like with other gradients, huh?):
10 |
11 | * You need to provide `colors` for gradient - that is obvious, right?
12 | * You need to provide `location`s of control points. Locations are in metal coordinate space - that one is a bit scarier. Relax, it is just values in -1...1
13 | * You need to provide how *turbulent* your mesh is. This is `uTangent` and `vTangent` exists for.
14 |
15 | Again, **don't be scared**. You have all the randomizers out of the box and they are pretty easy to use.
16 |
17 | Now, to the code:
18 |
19 | ```swift
20 | typealias MeshColor = SIMD3
21 |
22 | // You can provide custom `locationRandomizer` and `turbulencyRandomizer` for advanced usage
23 | var meshRandomizer = MeshRandomizer(colorRandomizer: MeshRandomizer.arrayBasedColorRandomizer(availableColors: meshColors))
24 |
25 | private var meshColors: [simd_float3] {
26 | return [
27 | MeshRandomizer.randomColor(),
28 | MeshRandomizer.randomColor(),
29 | MeshRandomizer.randomColor(),
30 | ]
31 | }
32 |
33 | // This methods prepares the grid model that will be sent to metal for rendering
34 | func generatePlainGrid(size: Int = 6) -> Grid {
35 | let preparationGrid = Grid(repeating: .zero, width: size, height: size)
36 |
37 | // At first we create grid without randomisation. This is smooth mesh gradient without
38 | // any turbulency and overlaps
39 | var result = MeshGenerator.generate(colorDistribution: preparationGrid)
40 |
41 | // And here we shuffle the grid using randomizer that we created
42 | for x in stride(from: 0, to: result.width, by: 1) {
43 | for y in stride(from: 0, to: result.height, by: 1) {
44 | meshRandomizer.locationRandomizer(&result[x, y].location, x, y, result.width, result.height)
45 | meshRandomizer.turbulencyRandomizer(&result[x, y].uTangent, x, y, result.width, result.height)
46 | meshRandomizer.turbulencyRandomizer(&result[x, y].vTangent, x, y, result.width, result.height)
47 |
48 | meshRandomizer.colorRandomizer(&result[x, y].color, result[x, y].color, x, y, result.width, result.height)
49 | }
50 | }
51 |
52 | return result
53 | }
54 |
55 | ```
56 |
57 | ### SwiftUI
58 |
59 | ```swift
60 | // If you want just show static grid without any animations
61 | struct MyStaticGrid: View {
62 | var body: some View {
63 | MeshGradient(grid: generatePlainGrid())
64 | }
65 | }
66 |
67 | struct MyAnimatedGrid: View {
68 |
69 | // MeshRandomizer is a plain struct with just the functions. So you can dynamically change it!
70 | @State var meshRandomizer = MeshRandomizer(colorRandomizer: MeshRandomizer.arrayBasedColorRandomizer(availableColors: meshColors))
71 |
72 | var body: some View {
73 | MeshGradient(initialGrid: generatePlainGrid(),
74 | animatorConfiguration: .init(animationSpeedRange: 2 ... 4, meshRandomizer: meshRandomizer)))
75 | }
76 | }
77 | ```
78 |
79 | ### UIKit
80 |
81 | Use `MTKView` and `MetalMeshRenderer`. I hope you have minimal knowledge in metal rendering. Good luck ;)
82 | Check source code of SwiftUI `MeshGradient` implementation that just wraps `MTKView`.
83 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/ControlPoint.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import simd
4 | import SwiftUI
5 |
6 | /// Describes one mesh gradient entry in matrix.
7 | public struct ControlPoint {
8 |
9 | /// Color of the control point
10 | public var color: simd_float3
11 |
12 | /// Location of control point. Initially just interpolated from grid size. Change it to have overlapping in mesh
13 | public var location: simd_float2
14 |
15 | /// Defines turbulency in mesh gradient. Change to show more "sinusoidal" look
16 | public var uTangent: simd_float2
17 |
18 | /// Defines turbulency in mesh gradient. Change to show more "sinusoidal" look
19 | public var vTangent: simd_float2
20 |
21 | public init(color: simd_float3 = simd_float3(0, 0, 0),
22 | location: simd_float2 = simd_float2(0, 0),
23 | uTangent: simd_float2 = simd_float2(0, 0),
24 | vTangent: simd_float2 = simd_float2(0, 0)) {
25 | self.color = color
26 | self.location = location
27 | self.uTangent = uTangent
28 | self.vTangent = vTangent
29 | }
30 |
31 | }
32 |
33 | extension ControlPoint: VectorArithmetic, AdditiveArithmetic {
34 | public static func - (lhs: ControlPoint, rhs: ControlPoint) -> ControlPoint {
35 | .init(color: lhs.color - rhs.color,
36 | location: lhs.location - rhs.location,
37 | uTangent: lhs.uTangent - rhs.uTangent,
38 | vTangent: lhs.vTangent - rhs.vTangent)
39 | }
40 |
41 | public static func + (lhs: ControlPoint, rhs: ControlPoint) -> ControlPoint {
42 | .init(color: lhs.color + rhs.color,
43 | location: lhs.location + rhs.location,
44 | uTangent: lhs.uTangent + rhs.uTangent,
45 | vTangent: lhs.vTangent + rhs.vTangent)
46 | }
47 |
48 | public mutating func scale(by rhs: Double) {
49 | color.scale(by: rhs)
50 | location.scale(by: rhs)
51 | uTangent.scale(by: rhs)
52 | vTangent.scale(by: rhs)
53 | }
54 |
55 | public var magnitudeSquared: Double {
56 | color.magnitudeSquared + location.magnitudeSquared + uTangent.magnitudeSquared + vTangent.magnitudeSquared
57 | }
58 |
59 | public static var zero: ControlPoint {
60 | .init(color: .zero, location: .zero, uTangent: .zero, vTangent: .zero)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/DummyResources/please_swiftpm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patskovn/MeshGradient/f16b544336b7bdaf7e3eba4ac6cba50e1160220e/Sources/MeshGradient/DummyResources/please_swiftpm
--------------------------------------------------------------------------------
/Sources/MeshGradient/Grid.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | import simd
4 | import Accelerate
5 |
6 | /// A two-dimensional grid of `Element`.
7 | public struct Grid {
8 | public var elements: ContiguousArray
9 |
10 | public var width: Int
11 |
12 | public var height: Int
13 |
14 | public init(repeating element: Element, width: Int, height: Int) {
15 | self.width = width
16 | self.height = height
17 | self.elements = ContiguousArray(repeating: element, count: width * height)
18 | }
19 |
20 | public init(width: Int, array: () -> [Element]) {
21 | let arr = array()
22 | assert(arr.count.isMultiple(of: width))
23 | self.width = width
24 | self.height = arr.count / width
25 | self.elements = ContiguousArray(arr)
26 | }
27 |
28 | public func index(x: Int, y: Int) -> Int {
29 | x + y * width
30 | }
31 |
32 | public subscript(i: Int) -> Element {
33 | get {
34 | elements[i]
35 | }
36 | set {
37 | elements[i] = newValue
38 | }
39 | }
40 |
41 | public subscript(x: Int, y: Int) -> Element {
42 | get {
43 | self[index(x: x, y: y)]
44 | }
45 | set {
46 | self[index(x: x, y: y)] = newValue
47 | }
48 | }
49 | }
50 |
51 | extension Grid: Equatable where Element: Equatable {}
52 | extension Grid: Hashable where Element: Hashable {}
53 |
54 | extension Collection {
55 |
56 | /// Returns the element at the specified index if it is within bounds, otherwise nil.
57 | subscript (safe index: Index) -> Element? {
58 | return indices.contains(index) ? self[index] : nil
59 | }
60 | }
61 |
62 | extension Grid: VectorArithmetic, AdditiveArithmetic where Element: VectorArithmetic & AdditiveArithmetic {
63 | public static func - (lhs: Grid, rhs: Grid) -> Grid {
64 | var grid = Grid(repeating: .zero, width: lhs.width, height: lhs.height)
65 | for i in lhs.elements.indices {
66 | grid.elements[i] = lhs.elements[i] - (rhs.elements[safe: i] ?? Element.zero)
67 | }
68 | return grid
69 | }
70 |
71 | public static func + (lhs: Grid, rhs: Grid) -> Grid {
72 | var grid = Grid(repeating: .zero, width: lhs.width, height: lhs.height)
73 |
74 | for i in lhs.elements.indices {
75 | grid.elements[i] = lhs.elements[i] + (rhs.elements[safe: i] ?? Element.zero)
76 | }
77 | return grid
78 | }
79 |
80 | public mutating func scale(by rhs: Double) {
81 | for i in elements.indices {
82 | elements[i].scale(by: rhs)
83 | }
84 | }
85 |
86 | public var magnitudeSquared: Double {
87 | elements.reduce(0, { $0 + Double($1.magnitudeSquared) })
88 | }
89 |
90 | public static var zero: Grid {
91 | .init(repeating: Element.zero, width: 0, height: 0)
92 | }
93 | }
94 |
95 | extension SIMD2: VectorArithmetic, AdditiveArithmetic where Scalar == Float {
96 | public mutating func scale(by rhs: Double) {
97 | self = self * SIMD2(x: Float(rhs), y: Float(rhs))
98 | }
99 |
100 | public var magnitudeSquared: Double {
101 | Double(x*x + y*y)
102 | }
103 | }
104 |
105 | extension SIMD3: VectorArithmetic, AdditiveArithmetic where Scalar == Float {
106 | public mutating func scale(by rhs: Double) {
107 | self = self * SIMD3(x: Float(rhs), y: Float(rhs), z: Float(rhs))
108 | }
109 |
110 | public var magnitudeSquared: Double {
111 | Double(x*x+y*y+z*z)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLBufferPool.swift:
--------------------------------------------------------------------------------
1 |
2 | import Metal
3 |
4 | final class MTLBufferPool {
5 |
6 | private var pool: [Int: [UInt: ContiguousArray]] = [:]
7 | private let device: MTLDevice
8 | private let monitor = NSObject()
9 |
10 | init(device: MTLDevice) {
11 | self.device = device
12 | }
13 |
14 | subscript(length: Int, resourceOptions: MTLResourceOptions) -> MTLBuffer? {
15 | get {
16 | objc_sync_enter(monitor)
17 | defer { objc_sync_exit(monitor) }
18 |
19 | var availableBuffer = pool[length, default: [:]][resourceOptions.rawValue, default: []]
20 | if availableBuffer.isEmpty {
21 | return device.makeBuffer(length: length, options: resourceOptions)
22 |
23 | } else {
24 | defer { pool[length, default: [:]][resourceOptions.rawValue] = availableBuffer }
25 | return availableBuffer.removeLast()
26 | }
27 | }
28 | set {
29 | objc_sync_enter(monitor)
30 | defer { objc_sync_exit(monitor) }
31 |
32 | guard let newValue = newValue else { return }
33 | pool[length, default: [:]][resourceOptions.rawValue, default: []].append(newValue)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeHermitPatchMatrix.metal:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #import "../MeshGradientCHeaders/include/MetalMeshShaderTypes.h"
4 |
5 | using namespace metal;
6 |
7 | static vector_float2 surfacePoint(float u,
8 | float v,
9 | float4x4 H,
10 | float4x4 H_T,
11 | float4x4 X,
12 | float4x4 Y) {
13 | float4 U = float4 {
14 | u * u * u, u * u, u, 1
15 | };
16 | float4 V = float4 {
17 | v * v * v, v * v, v, 1
18 | };
19 |
20 | return float2 {
21 | dot(V, U * H * X * H_T),
22 | dot(V, U * H * Y * H_T)
23 | };
24 | }
25 |
26 | static vector_float4 colorPoint(float u,
27 | float v,
28 | float4x4 H,
29 | float4x4 H_T,
30 | float4x4 R,
31 | float4x4 G,
32 | float4x4 B) {
33 |
34 | float4 U = float4 {
35 | u * u * u, u * u, u, 1
36 | };
37 | float4 V = float4 {
38 | v * v * v, v * v, v, 1
39 | };
40 |
41 | return float4 {
42 | dot(V, U * H * R * H_T),
43 | dot(V, U * H * G * H_T),
44 | dot(V, U * H * B * H_T),
45 | 1
46 | };
47 | }
48 |
49 | kernel void computeHermitPatchMatrix(device const MeshIntermediateVertex* inVertices [[buffer(ComputeMeshFinalInputIndexVertices)]],
50 | constant uint *width [[buffer(ComputeMeshFinalInputIndexWidth)]],
51 | constant uint *subdivisions [[buffer(ComputeMeshFinalInputIndexSubdivisions)]],
52 | device MeshVertex* result [[buffer(ComputeMeshFinalInputIndexResult)]],
53 | constant vector_uint3 *computeSize [[buffer(ComputeMeshFinalInputIndexCalculationSize)]],
54 | vector_uint3 index [[thread_position_in_grid]]) {
55 |
56 | vector_uint3 computeSizeValue = *computeSize;
57 |
58 | if (index.x >= computeSizeValue.x || index.y >= computeSizeValue.y || index.z >= computeSizeValue.z) {
59 | return;
60 | }
61 |
62 | uint w = *width;
63 | uint s = *subdivisions;
64 |
65 | uint i = index.z;
66 |
67 | uint u_i = index.y;
68 | uint v_i = index.x;
69 |
70 | MeshIntermediateVertex vert = inVertices[i];
71 | uint resX = vert.x * s + u_i;
72 | uint resY = vert.y * s + v_i;
73 | uint resI = resX + resY * w;
74 |
75 | float u = float(u_i) / float(s-1);
76 | float v = float(v_i) / float(s-1);
77 |
78 | float4x4 H = float4x4 {
79 | { 2, -3, 0, 1 },
80 | { -2, 3, 0, 0 },
81 | { 1, -2, 1, 0 },
82 | { 1, -1, 0, 0 },
83 | };
84 | float4x4 H_T = transpose(H);
85 |
86 | MeshVertex resultVertex = {
87 | surfacePoint(u, v, H, H_T, vert.X, vert.Y),
88 | colorPoint(u, v, H, H_T, vert.R, vert.G, vert.B),
89 | };
90 | result[resI] = resultVertex;
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeHermitPatchMatrixFunction.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import Metal
4 | import simd
5 | @_implementationOnly import MeshGradientCHeaders
6 |
7 | final class MTLComputeHermitPatchMatrixFunction {
8 |
9 | private let pipelineState: MTLComputePipelineState
10 |
11 | init(device: MTLDevice, library: MTLLibrary) throws {
12 | guard let computeHermitPatchMatrixFunction = library.makeFunction(name: "computeHermitPatchMatrix")
13 | else { throw MeshGradientError.metalFunctionNotFound(name: "computeHermitPatchMatrix") }
14 |
15 | self.pipelineState = try device.makeComputePipelineState(function: computeHermitPatchMatrixFunction)
16 | }
17 |
18 | func call(subdivisions: Int,
19 | resultBuffer: MTLBuffer,
20 | intermediateResultBuffer: MTLBuffer,
21 | gridWidth: Int,
22 | intermediateResultBufferSize: Int,
23 | commandBuffer: MTLCommandBuffer) {
24 |
25 | guard let computeEncoder = commandBuffer.makeComputeCommandEncoder()
26 | else {
27 | assertionFailure()
28 | return
29 | }
30 | computeEncoder.setComputePipelineState(pipelineState)
31 |
32 | computeEncoder.setBuffer(intermediateResultBuffer,
33 | offset: 0,
34 | index: Int(ComputeMeshFinalInputIndexVertices.rawValue))
35 |
36 | var width = gridWidth
37 | let depth = intermediateResultBufferSize
38 |
39 | computeEncoder.setBytes(&width,
40 | length: MemoryLayout.size(ofValue: width),
41 | index: Int(ComputeMeshFinalInputIndexWidth.rawValue))
42 |
43 | var inSubdivisions = UInt32(subdivisions)
44 | computeEncoder.setBytes(&inSubdivisions,
45 | length: MemoryLayout.size(ofValue: inSubdivisions),
46 | index: Int(ComputeMeshFinalInputIndexSubdivisions.rawValue))
47 |
48 | computeEncoder.setBuffer(resultBuffer,
49 | offset: 0,
50 | index: Int(ComputeMeshFinalInputIndexResult.rawValue))
51 |
52 | let computeSize = MTLSize(width: subdivisions,
53 | height: subdivisions,
54 | depth: depth)
55 |
56 | var calculationSizeParameter = SIMD3(Int32(computeSize.width),
57 | Int32(computeSize.height),
58 | Int32(computeSize.depth))
59 |
60 | computeEncoder.setBytes(&calculationSizeParameter,
61 | length: MemoryLayout.size(ofValue: calculationSizeParameter),
62 | index: Int(ComputeMeshFinalInputIndexCalculationSize.rawValue))
63 |
64 | let threadGroupSize = MTLSize(width: min(intermediateResultBufferSize, pipelineState.maxTotalThreadsPerThreadgroup),
65 | height: 1,
66 | depth: 1)
67 |
68 | computeEncoder.dispatchThreadgroups(computeSize,
69 | threadsPerThreadgroup: threadGroupSize)
70 |
71 | computeEncoder.endEncoding()
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeMeshTrianglePrimitives.metal:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | #import "../MeshGradientCHeaders/include/MetalMeshShaderTypes.h"
5 |
6 | using namespace metal;
7 |
8 | MeshVertex meshVertex(device const MeshVertex* inVertices, uint x, uint y, uint width, uint tag) {
9 | return inVertices[x + y * width];
10 | };
11 |
12 | kernel void computeMeshTrianglePrimitives(device const MeshVertex* inVertices [[buffer(ComputeMeshTrianglesIndexVertices)]],
13 | constant uint *width [[buffer(ComputeMeshTrianglesIndexWidth)]],
14 | constant uint *height [[buffer(ComputeMeshTrianglesIndexHeight)]],
15 | device MeshVertex* result [[buffer(ComputeMeshTrianglesIndexResult)]],
16 | constant vector_uint2 *computeSize [[buffer(ComputeMeshTrianglesIndexCalculationSize)]],
17 | vector_uint2 index [[thread_position_in_grid]]) {
18 | vector_uint2 computeSizeValue = *computeSize;
19 |
20 | if (index.x >= computeSizeValue.x || index.y >= computeSizeValue.y) {
21 | return;
22 | }
23 |
24 | uint w = *width;
25 |
26 | uint y = index.y;
27 | uint x = index.x;
28 |
29 | uint i = (x + y * (w - 1)) * 6;
30 |
31 | MeshVertex p00 = meshVertex(inVertices, x , y , w, 0);
32 | MeshVertex p10 = meshVertex(inVertices, x + 1, y , w, 10);
33 | MeshVertex p01 = meshVertex(inVertices, x , y + 1, w, 1);
34 | MeshVertex p11 = meshVertex(inVertices, x + 1, y + 1, w, 11);
35 |
36 | result[i+0] = p00;
37 | result[i+1] = p10;
38 | result[i+2] = p11;
39 |
40 | result[i+3] = p11;
41 | result[i+4] = p01;
42 | result[i+5] = p00;
43 | }
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeMeshTrianglePrimitivesFunction.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import Metal
4 | import simd
5 | @_implementationOnly import MeshGradientCHeaders
6 |
7 | final class MTLComputeMeshTrianglePrimitivesFunction {
8 |
9 | private let pipelineState: MTLComputePipelineState
10 |
11 | init(device: MTLDevice, library: MTLLibrary) throws {
12 | guard let computeMeshTrianglePrimitivesFunction = library.makeFunction(name: "computeMeshTrianglePrimitives")
13 | else { throw MeshGradientError.metalFunctionNotFound(name: "computeMeshTrianglePrimitives") }
14 |
15 | self.pipelineState = try device.makeComputePipelineState(function: computeMeshTrianglePrimitivesFunction)
16 | }
17 |
18 | func call(gridSize: (width: Int, height: Int),
19 | resultTrianglesBuffer: MTLBuffer,
20 | finalVertices: MTLBuffer,
21 | finalVerticesSize: Int,
22 | commandBuffer: MTLCommandBuffer) {
23 |
24 | guard let computeEncoder = commandBuffer.makeComputeCommandEncoder()
25 | else {
26 | assertionFailure()
27 | return
28 | }
29 | computeEncoder.setComputePipelineState(pipelineState)
30 |
31 | computeEncoder.setBuffer(finalVertices,
32 | offset: 0,
33 | index: Int(ComputeMeshTrianglesIndexVertices.rawValue))
34 |
35 | var (width, height) = gridSize
36 | computeEncoder.setBytes(&width,
37 | length: MemoryLayout.size(ofValue: width),
38 | index: Int(ComputeMeshTrianglesIndexWidth.rawValue))
39 |
40 | computeEncoder.setBytes(&height,
41 | length: MemoryLayout.size(ofValue: height),
42 | index: Int(ComputeMeshTrianglesIndexHeight.rawValue))
43 |
44 | computeEncoder.setBuffer(resultTrianglesBuffer,
45 | offset: 0,
46 | index: Int(ComputeMeshTrianglesIndexResult.rawValue))
47 |
48 | let computeSize = MTLSize(width: width - 1,
49 | height: height - 1,
50 | depth: 1)
51 |
52 | var calculationSizeParameter = SIMD2(Int32(computeSize.width), Int32(computeSize.height))
53 | computeEncoder.setBytes(&calculationSizeParameter,
54 | length: MemoryLayout.size(ofValue: calculationSizeParameter),
55 | index: Int(ComputeMeshTrianglesIndexCalculationSize.rawValue))
56 |
57 | let threadGroupSize = MTLSize(width: min(finalVerticesSize, pipelineState.maxTotalThreadsPerThreadgroup),
58 | height: 1,
59 | depth: 1)
60 |
61 | computeEncoder.dispatchThreadgroups(computeSize,
62 | threadsPerThreadgroup: threadGroupSize)
63 |
64 | computeEncoder.endEncoding()
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeNoise.metal:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | #import "../MeshGradientCHeaders/include/MetalMeshShaderTypes.h"
5 |
6 | using namespace metal;
7 |
8 |
9 | constant uint NOISE_DIM = 512;
10 |
11 | // Noise Declarations
12 | float rand(int x, int y);
13 | float smoothNoise(float x, float y);
14 |
15 | half4 noiseColor(int x, int y, bool isSmooth, float alpha);
16 |
17 | // The Kernel
18 | kernel void computeNoize(texture2d outTexture [[texture(ComputeNoiseInputIndexOutputTexture)]],
19 | constant NoiseUniforms &uniforms [[buffer(ComputeNoiseInputIndexUniforms)]],
20 | uint2 gid [[thread_position_in_grid]])
21 | {
22 | half4 outColor = noiseColor(gid.x,
23 | gid.y,
24 | uniforms.isSmooth == 1,
25 | uniforms.noiseAlpha);
26 |
27 | outTexture.write(outColor, gid);
28 | }
29 |
30 | // Noise
31 | half4 noiseColor(int x, int y, bool isSmooth, float alpha)
32 | {
33 | float colorVal;
34 |
35 | float xVal = x;
36 | float yVal = y;
37 |
38 | float xZoomed = xVal / 0.5;
39 | float yZoomed = yVal / 0.5;
40 |
41 | colorVal = 256.0 * rand(xZoomed, yZoomed);
42 |
43 | if (isSmooth) {
44 | // colorVal = 256.0 * smoothNoise(xZoomed, yZoomed);
45 | }
46 |
47 | return half4(half(colorVal) / 255.0, half(colorVal) / 255.0, half(colorVal) / 255.0, alpha);
48 | }
49 |
50 | // Noise Functions
51 | float smoothNoise(float x, float y)
52 | {
53 | // Get the truncated x, y, and z values
54 | int intX = x;
55 | int intY = y;
56 |
57 | // Get the fractional reaminder of x, y, and z
58 | float fractX = x - intX;
59 | float fractY = y - intY;
60 |
61 | // Get first whole number before
62 | int x1 = (intX + NOISE_DIM) % NOISE_DIM;
63 | int y1 = (intY + NOISE_DIM) % NOISE_DIM;
64 |
65 | // Get the number after
66 | int x2 = (x1 + NOISE_DIM - 1) % NOISE_DIM;
67 | int y2 = (y1 + NOISE_DIM - 1) % NOISE_DIM;
68 |
69 | // interpolate the noise
70 | float value = 0.0;
71 | value += fractX * fractY * rand(x1,y1);
72 | value += fractX * (1 - fractY) * rand(x1,y2);
73 | value += (1 - fractX) * fractY * rand(x2,y1);
74 | value += (1 - fractX) * (1 - fractY) * rand(x2,y2);
75 |
76 | return value;
77 | }
78 |
79 | // Generate a random float in the range [0.0f, 1.0f] using x, y, and z (based on the xor128 algorithm)
80 | float rand(int x, int y)
81 | {
82 | int seed = x + y * 57 * 241;
83 | seed = (seed<< 13) ^ seed;
84 | return (( 1.0 - ( (seed * (seed * seed * 15731 + 789221) + 1376312589) & 2147483647) / 1073741824.0f) + 1.0f) / 2.0f;
85 | }
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeNoiseFunction.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | import Foundation
4 | import Metal
5 | import simd
6 | @_implementationOnly import MeshGradientCHeaders
7 |
8 | final class MTLComputeNoiseFunction {
9 |
10 | private var _noiseTexture: MTLTexture?
11 |
12 | private let device: MTLDevice
13 | private let pipelineState: MTLComputePipelineState
14 |
15 | init(device: MTLDevice, library: MTLLibrary) throws {
16 | self.device = device
17 |
18 | guard let computeNoiseFunction = library.makeFunction(name: "computeNoize")
19 | else { throw MeshGradientError.metalFunctionNotFound(name: "computeNoize") }
20 |
21 | self.pipelineState = try device.makeComputePipelineState(function: computeNoiseFunction)
22 | }
23 |
24 | func call(viewportSize: simd_float2, pixelFormat: MTLPixelFormat, commandQueue: MTLCommandQueue, uniforms: NoiseUniforms) -> MTLTexture? {
25 | guard uniforms.noiseAlpha > 0 else { return nil }
26 |
27 | guard let commandBuffer = commandQueue.makeCommandBuffer() else { return nil }
28 | let width = Int(viewportSize.x)
29 | let height = Int(viewportSize.y)
30 |
31 | if width == 0 || height == 0 {
32 | return nil
33 | }
34 |
35 | if let noiseTexture = _noiseTexture, noiseTexture.width == width, noiseTexture.height == height {
36 | return noiseTexture
37 | }
38 | let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: pixelFormat,
39 | width: width,
40 | height: height,
41 | mipmapped: false)
42 | textureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget, .pixelFormatView]
43 | guard let noiseTexture = device.makeTexture(descriptor: textureDescriptor),
44 | let encoder = commandBuffer.makeComputeCommandEncoder()
45 | else { return nil }
46 |
47 | let threadgroupCounts = MTLSize(width: 8, height: 8, depth: 1)
48 | let threadgroups = MTLSize(width: noiseTexture.width / threadgroupCounts.width,
49 | height: noiseTexture.height / threadgroupCounts.height,
50 | depth: 1)
51 |
52 | if threadgroups.height == 0 || threadgroups.width == 0 {
53 | encoder.endEncoding()
54 | return nil
55 | }
56 |
57 | encoder.setComputePipelineState(pipelineState)
58 |
59 | encoder.setTexture(noiseTexture, index: Int(ComputeNoiseInputIndexOutputTexture.rawValue))
60 |
61 | var uniforms = uniforms
62 | encoder.setBytes(&uniforms,
63 | length: MemoryLayout.size(ofValue: uniforms),
64 | index: Int(ComputeNoiseInputIndexUniforms.rawValue))
65 |
66 | encoder.dispatchThreadgroups(threadgroups,
67 | threadsPerThreadgroup: threadgroupCounts)
68 |
69 | encoder.endEncoding()
70 |
71 | commandBuffer.commit()
72 | commandBuffer.waitUntilCompleted()
73 |
74 | self._noiseTexture = noiseTexture
75 | return noiseTexture
76 | }
77 |
78 | }
79 | extension MTLTexture {
80 | func getPixels(mipmapLevel: Int = 0) -> UnsafeMutablePointer {
81 | let fromRegion = MTLRegionMake2D(0, 0, self.width, self.height)
82 | let bytesPerRow = 4 * self.width
83 | let data = UnsafeMutablePointer.allocate(capacity: bytesPerRow * self.height)
84 |
85 | self.getBytes(data, bytesPerRow: bytesPerRow, from: fromRegion, mipmapLevel: mipmapLevel)
86 | return data
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeShuffleCoefficients.metal:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | #import "../MeshGradientCHeaders/include/MetalMeshShaderTypes.h"
5 |
6 | using namespace metal;
7 |
8 | static float4x4 meshXCoefficients(MeshControlPoint p00,
9 | MeshControlPoint p01,
10 | MeshControlPoint p10,
11 | MeshControlPoint p11) {
12 |
13 | return float4x4 {
14 | { p00.location.x, p10.location.x, p00.uTangent.x, p10.uTangent.x },
15 | { p01.location.x, p11.location.x, p01.uTangent.x, p11.uTangent.x },
16 | { p00.vTangent.x, p10.vTangent.x, 0, 0 },
17 | { p01.vTangent.x, p11.vTangent.x, 0, 0 },
18 | };
19 | }
20 |
21 | static float4x4 meshYCoefficients(MeshControlPoint p00,
22 | MeshControlPoint p01,
23 | MeshControlPoint p10,
24 | MeshControlPoint p11) {
25 |
26 |
27 | return float4x4 {
28 | { p00.location.y, p10.location.y, p00.uTangent.y, p10.uTangent.y },
29 | { p01.location.y, p11.location.y, p01.uTangent.y, p11.uTangent.y },
30 | { p00.vTangent.y, p10.vTangent.y, 0, 0 },
31 | { p01.vTangent.y, p11.vTangent.y, 0, 0 },
32 | };
33 | }
34 |
35 | static float4x4 colorCoefficients(float p00,
36 | float p01,
37 | float p10,
38 | float p11) {
39 |
40 | return float4x4 {
41 | { p00, p10, 0, 0 },
42 | { p01, p11, 0, 0 },
43 | { 0, 0, 0, 0 },
44 | { 0, 0, 0, 0 },
45 | };
46 | }
47 |
48 | MeshControlPoint meshControlPoint(device const MeshControlPoint* inVertices, uint x, uint y, uint width) {
49 | return inVertices[x + y * width];
50 | }
51 |
52 |
53 | kernel void shuffleCoefficients(device const MeshControlPoint* inVertices [[buffer(ComputeMeshVertexInputIndexVertices)]],
54 | constant vector_uint2 *gridSize [[buffer(ComputeMeshVertexInputIndexGridSize)]],
55 | device MeshIntermediateVertex* result [[buffer(ComputeMeshVertexInputIndexResult)]],
56 | constant vector_uint2 *computeSize [[buffer(ComputeMeshVertexInputCalculationSize)]],
57 | vector_uint2 index [[thread_position_in_grid]]) {
58 | vector_uint2 computeSizeValue = *computeSize;
59 |
60 | if (index.x >= computeSizeValue.x || index.y >= computeSizeValue.y) {
61 | return;
62 | }
63 |
64 | uint width = (*gridSize).x;
65 |
66 | uint x = index.x;
67 | uint y = index.y;
68 |
69 | uint i = index.x + index.y * (width - 1);
70 |
71 | MeshControlPoint p00 = meshControlPoint(inVertices, x, y, width);
72 | MeshControlPoint p01 = meshControlPoint(inVertices, x, y + 1, width);
73 | MeshControlPoint p10 = meshControlPoint(inVertices, x + 1, y, width);
74 | MeshControlPoint p11 = meshControlPoint(inVertices, x + 1, y + 1, width);
75 |
76 | float4x4 X = meshXCoefficients(p00, p01, p10, p11);
77 | float4x4 Y = meshYCoefficients(p00, p01, p10, p11);
78 |
79 | float4x4 R = colorCoefficients(p00.color.x,
80 | p01.color.x,
81 | p10.color.x,
82 | p11.color.x);
83 |
84 | float4x4 G = colorCoefficients(p00.color.y,
85 | p01.color.y,
86 | p10.color.y,
87 | p11.color.y);
88 |
89 | float4x4 B = colorCoefficients(p00.color.z,
90 | p01.color.z,
91 | p10.color.z,
92 | p11.color.z);
93 |
94 | MeshIntermediateVertex intermediateVertex = {
95 | X,
96 | Y,
97 | R,
98 | G,
99 | B,
100 | x,
101 | y,
102 | };
103 | result[i] = intermediateVertex;
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLComputeShuffleCoefficientsFunction.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import Metal
4 | import simd
5 | @_implementationOnly import MeshGradientCHeaders
6 |
7 | final class MTLComputeShuffleCoefficientsFunction {
8 |
9 | private let pipelineState: MTLComputePipelineState
10 | private let bufferPool: MTLBufferPool
11 |
12 | init(device: MTLDevice, library: MTLLibrary, bufferPool: MTLBufferPool) throws {
13 | guard let shuffleCoefficientsFunction = library.makeFunction(name: "shuffleCoefficients")
14 | else { throw MeshGradientError.metalFunctionNotFound(name: "shuffleCoefficients") }
15 |
16 | self.pipelineState = try device.makeComputePipelineState(function: shuffleCoefficientsFunction)
17 | self.bufferPool = bufferPool
18 | }
19 |
20 | func call(grid: Grid, intermediateResultBuffer: MTLBuffer, commandBuffer: MTLCommandBuffer) {
21 | let buffer = grid.elements.map {
22 | return MeshControlPoint(location: $0.location,
23 | uTangent: $0.uTangent,
24 | vTangent: $0.vTangent,
25 | color: vector_float4($0.color, 1))
26 | }
27 |
28 | let bufferLength = MemoryLayout.stride * buffer.count
29 | guard
30 | let computeEncoder = commandBuffer.makeComputeCommandEncoder(),
31 | let inputBuffer = bufferPool[bufferLength, .storageModeShared]
32 | else {
33 | assertionFailure()
34 | return
35 | }
36 | computeEncoder.setComputePipelineState(pipelineState)
37 |
38 | commandBuffer.addCompletedHandler { _ in
39 | self.bufferPool[bufferLength, .storageModeShared] = inputBuffer
40 | }
41 |
42 | let rawPointer = inputBuffer.contents()
43 | let typedPointer = rawPointer.bindMemory(to: MeshControlPoint.self, capacity: bufferLength)
44 | let bufferedPointer = UnsafeBufferPointer(start: typedPointer, count: buffer.count)
45 | let mutatingPointer = UnsafeMutableBufferPointer(mutating: bufferedPointer)
46 |
47 | for i in stride(from: 0, to: buffer.count, by: 1) {
48 | mutatingPointer[i] = buffer[i]
49 | }
50 |
51 | computeEncoder.setBuffer(inputBuffer,
52 | offset: 0,
53 | index: Int(ComputeMeshVertexInputIndexVertices.rawValue))
54 |
55 | var gridSize = vector_uint2(UInt32(grid.width), UInt32(grid.height))
56 | computeEncoder.setBytes(&gridSize,
57 | length: MemoryLayout.size(ofValue: gridSize),
58 | index: Int(ComputeMeshVertexInputIndexGridSize.rawValue))
59 |
60 | computeEncoder.setBuffer(intermediateResultBuffer,
61 | offset: 0,
62 | index: Int(ComputeMeshVertexInputIndexResult.rawValue))
63 |
64 | let computeSize = MTLSize(width: grid.width - 1,
65 | height: grid.height - 1,
66 | depth: 1)
67 |
68 | var calculationSizeParameter = SIMD2(Int32(computeSize.width), Int32(computeSize.height))
69 | computeEncoder.setBytes(&calculationSizeParameter,
70 | length: MemoryLayout.size(ofValue: calculationSizeParameter),
71 | index: Int(ComputeMeshVertexInputCalculationSize.rawValue))
72 |
73 | let threadGroupSize = MTLSize(width: min(grid.elements.count, pipelineState.maxTotalThreadsPerThreadgroup),
74 | height: 1,
75 | depth: 1)
76 |
77 | computeEncoder.dispatchThreadgroups(computeSize,
78 | threadsPerThreadgroup: threadGroupSize)
79 |
80 | computeEncoder.endEncoding()
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLDrawMeshTriangles.metal:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | using namespace metal;
4 |
5 | #import "../MeshGradientCHeaders/include/MetalMeshShaderTypes.h"
6 |
7 | // Vertex shader outputs and fragment shader inputs
8 | struct RasterizerData
9 | {
10 | // The [[position]] attribute of this member indicates that this value
11 | // is the clip space position of the vertex when this structure is
12 | // returned from the vertex function.
13 | float4 position [[position]];
14 |
15 | // Since this member does not have a special attribute, the rasterizer
16 | // interpolates its value with the values of the other triangle vertices
17 | // and then passes the interpolated value to the fragment shader for each
18 | // fragment in the triangle.
19 | float4 color;
20 | float2 textureCoordinate;
21 | };
22 |
23 | vertex RasterizerData
24 | prepareToDrawShader(uint vertexID [[vertex_id]],
25 | constant MeshVertex *vertices [[buffer(0)]])
26 | {
27 | RasterizerData out;
28 |
29 | out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
30 | out.position.xy = vertices[vertexID].position.xy;
31 |
32 | // Pass the input color directly to the rasterizer.
33 | out.color = vertices[vertexID].color;
34 |
35 | out.textureCoordinate = float2(out.position.x / 2 + 0.5, -out.position.y / 2 + 0.5);
36 |
37 | return out;
38 | }
39 |
40 | fragment float4 drawRasterizedMesh(RasterizerData in [[stage_in]],
41 | texture2d noiseTexture [[ texture(DrawMeshTextureIndexBaseColor) ]])
42 | {
43 |
44 |
45 | constexpr sampler linear(coord::normalized,
46 | address::clamp_to_edge,
47 | filter::linear);
48 |
49 | // Sample the texture to obtain a color
50 | const float4 colorSample = float4(noiseTexture.sample(linear, in.textureCoordinate));
51 |
52 | const float3 outRGB = colorSample.w * colorSample.xyz + (1 - colorSample.w) * in.color.xyz;
53 |
54 | return float4(outRGB, in.color.w);
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MTLDrawMeshTrianglesFunction.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import Metal
4 | import MetalKit
5 | import simd
6 | @_implementationOnly import MeshGradientCHeaders
7 |
8 | final class MTLDrawMeshTrianglesFunction {
9 |
10 | private let pipelineState: MTLRenderPipelineState
11 |
12 | init(device: MTLDevice, library: MTLLibrary, mtkView: MTKView) throws {
13 | guard let prepareToDrawFunction = library.makeFunction(name: "prepareToDrawShader")
14 | else { throw MeshGradientError.metalFunctionNotFound(name: "prepareToDrawShader") }
15 |
16 | guard let drawMeshFunction = library.makeFunction(name: "drawRasterizedMesh")
17 | else { throw MeshGradientError.metalFunctionNotFound(name: "drawRasterizedMesh") }
18 |
19 | let pipelineDescriptor = MTLRenderPipelineDescriptor()
20 | pipelineDescriptor.label = "Draw mesh pipeline"
21 | pipelineDescriptor.vertexFunction = prepareToDrawFunction
22 | pipelineDescriptor.fragmentFunction = drawMeshFunction
23 | pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
24 |
25 | self.pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
26 | }
27 |
28 | func call(meshVertices: MTLBuffer,
29 | noise: MTLTexture?,
30 | meshVerticesCount: Int,
31 | view: MTKView,
32 | commandBuffer: MTLCommandBuffer,
33 | viewportSize: simd_float2) {
34 |
35 | assert(meshVerticesCount.isMultiple(of: 3))
36 | guard let renderPassDescriptor = view.currentRenderPassDescriptor,
37 | let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
38 | else {
39 | assertionFailure()
40 | return
41 | }
42 |
43 | renderEncoder.setViewport(
44 | MTLViewport(originX: 0,
45 | originY: 0,
46 | width: Double(viewportSize.x),
47 | height: Double(viewportSize.y),
48 | znear: 0,
49 | zfar: 1)
50 | )
51 |
52 | renderEncoder.setRenderPipelineState(pipelineState)
53 |
54 | renderEncoder.setFragmentTexture(noise,
55 | index: Int(DrawMeshTextureIndexBaseColor.rawValue))
56 |
57 | renderEncoder.setVertexBuffer(meshVertices,
58 | offset: 0,
59 | index: 0)
60 | for i in stride(from: 0, to: meshVerticesCount, by: 3) {
61 | renderEncoder.drawPrimitives(type: .triangle,
62 | vertexStart: i,
63 | vertexCount: 3)
64 | }
65 |
66 | renderEncoder.endEncoding()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MeshAnimator.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import simd
4 |
5 | /// Stores current and next state of the mesh. Used for animatable meshes
6 | public final class MeshAnimator: MeshDataProvider {
7 |
8 | public struct Configuration {
9 |
10 | /// - Parameters:
11 | /// - framesPerSecond: Preferred framerate get that from MTKView
12 | /// - animationSpeedRange: Range of animation duration in the mesh, literally. The less the faster
13 | /// - meshRandomizer: Randomisation functions for mesh
14 | public init(framesPerSecond: Int = 60, animationSpeedRange: ClosedRange = 2...5, meshRandomizer: MeshRandomizer) {
15 | self.framesPerSecond = framesPerSecond
16 | self.animationSpeedRange = animationSpeedRange
17 | self.meshRandomizer = meshRandomizer
18 | }
19 |
20 | public var framesPerSecond: Int
21 | public var animationSpeedRange: ClosedRange
22 | public var meshRandomizer: MeshRandomizer
23 | }
24 |
25 | private struct AnimationFrameControlPoint {
26 | let finalControlPoint: ControlPoint
27 | let startPoint: ControlPoint
28 | var completionFactor: Double // 0...1
29 | let scaleFactor: Double
30 |
31 | mutating func bumpNextFrame() -> ControlPoint {
32 | completionFactor += scaleFactor
33 | var step = (finalControlPoint - startPoint)
34 |
35 | let easedCompletionFactor = completionFactor * completionFactor * (3 - 2 * completionFactor);
36 | step.scale(by: easedCompletionFactor)
37 |
38 | return startPoint + step
39 | }
40 |
41 | static var zero: AnimationFrameControlPoint {
42 | .init(finalControlPoint: .zero, startPoint: .zero, completionFactor: .zero, scaleFactor: .zero)
43 | }
44 | }
45 |
46 | private let initialGrid: Grid
47 | public var configuration: Configuration
48 | private var animationParameters: Grid
49 |
50 | public init(grid: Grid, configuration: Configuration) {
51 | self.initialGrid = grid
52 | self.configuration = configuration
53 |
54 | self.animationParameters = Grid(repeating: .zero, width: grid.width, height: grid.height)
55 |
56 | for y in 0 ..< animationParameters.height {
57 | for x in 0 ..< animationParameters.width {
58 | animationParameters[x, y] = generateNextAnimationEndpoint(x: x, y: y, gridWidth: grid.width, gridHeight: grid.height, startPoint: grid[x, y])
59 | }
60 | }
61 | }
62 |
63 | public var grid: Grid {
64 | var resultGrid = Grid(repeating: .zero,
65 | width: animationParameters.width,
66 | height: animationParameters.height)
67 |
68 | for y in 0 ..< animationParameters.height {
69 | for x in 0 ..< animationParameters.width {
70 | let i = animationParameters.index(x: x, y: y)
71 | resultGrid[i] = animationParameters[i].bumpNextFrame()
72 | if animationParameters[i].completionFactor >= 1 {
73 | animationParameters[i] = generateNextAnimationEndpoint(x: x, y: y, gridWidth: resultGrid.width, gridHeight: resultGrid.height, startPoint: resultGrid[i])
74 | }
75 | }
76 | }
77 | return resultGrid
78 | }
79 |
80 | private func generateNextAnimationEndpoint(x: Int, y: Int, gridWidth: Int, gridHeight: Int, startPoint: ControlPoint) -> AnimationFrameControlPoint {
81 | let animationDuration = Double.random(in: configuration.animationSpeedRange)
82 | let scaleFactor = (1 / Double(configuration.framesPerSecond)) / animationDuration
83 | var randomizedControlPoint = initialGrid[x, y]
84 |
85 | configuration.meshRandomizer.locationRandomizer(&randomizedControlPoint.location, x, y, gridWidth, gridHeight)
86 |
87 | configuration.meshRandomizer.turbulencyRandomizer(&randomizedControlPoint.uTangent, x, y, gridWidth, gridHeight)
88 | configuration.meshRandomizer.turbulencyRandomizer(&randomizedControlPoint.vTangent, x, y, gridWidth, gridHeight)
89 |
90 | configuration.meshRandomizer.colorRandomizer(&randomizedControlPoint.color, randomizedControlPoint.color, x, y, gridWidth, gridHeight)
91 |
92 | return AnimationFrameControlPoint(finalControlPoint: randomizedControlPoint,
93 | startPoint: startPoint,
94 | completionFactor: 0,
95 | scaleFactor: scaleFactor)
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MeshDataProvider.swift:
--------------------------------------------------------------------------------
1 |
2 | public protocol MeshDataProvider {
3 | var grid: Grid { get }
4 | }
5 |
6 | public class StaticMeshDataProvider: MeshDataProvider {
7 | public var grid: Grid
8 |
9 | public init(grid: Grid) {
10 | self.grid = grid
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MeshGenerator.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 |
4 | import simd
5 |
6 | public enum MeshGenerator {
7 |
8 | public typealias Color = simd_float3
9 |
10 | /// Linear interpolation between `min` and `max`.
11 | private static func lerp(_ f: S, _ min: S, _ max: S) -> S {
12 | min + f * (max - min)
13 | }
14 |
15 | public static func generate(colorDistribution: Grid) -> Grid {
16 | var grid = Grid(repeating: ControlPoint(),
17 | width: colorDistribution.width,
18 | height: colorDistribution.height)
19 |
20 | for y in 0 ..< grid.height {
21 | for x in 0 ..< grid.width {
22 | generateControlPoint(in: &grid, x: x, y: y)
23 | }
24 | }
25 | return grid
26 | }
27 |
28 | private static func generateControlPoint(in grid: inout Grid, x: Int, y: Int) {
29 | grid[x, y].location = simd_float2(
30 | lerp(Float(x) / Float(grid.width - 1), -1, 1),
31 | lerp(Float(y) / Float(grid.height - 1), -1, 1)
32 | )
33 |
34 | grid[x, y].uTangent.x = 2 / Float(grid.width - 1)
35 | grid[x, y].vTangent.y = 2 / Float(grid.height - 1)
36 | }
37 | }
38 |
39 | #if canImport(UIKit)
40 | import UIKit
41 | typealias NativeColor = UIColor
42 | #elseif canImport(AppKit)
43 | import AppKit
44 | typealias NativeColor = NSColor
45 | #endif
46 |
47 | public extension NativeColor {
48 | var toSIMD3: simd_float3 {
49 |
50 | var r: CGFloat = 0
51 | var g: CGFloat = 0
52 | var b: CGFloat = 0
53 | var o: CGFloat = 0
54 |
55 | getRed(&r, green: &g, blue: &b, alpha: &o)
56 |
57 | return .init(Float(r), Float(g), Float(b))
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MeshGradientError.swift:
--------------------------------------------------------------------------------
1 |
2 | enum MeshGradientError: Error {
3 | case metalFunctionNotFound(name: String)
4 | }
5 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MeshRandomizer.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import simd
4 |
5 | /// Stores randimisation information of the mesh. Data can be dynamically changes in SwiftUI views.
6 | /// Default initialiser provides empirically good random mesh. Free to change for your own needs.
7 | public struct MeshRandomizer {
8 | public init(locationRandomizer: @escaping MeshRandomizer.LocationRandomizer = MeshRandomizer.automaticallyRandomizeLocationYExceptTopAndBottomBasedOnGridSize(),
9 | turbulencyRandomizer: @escaping MeshRandomizer.TangentRandomizer = MeshRandomizer.randomizeTurbulencyExceptEdges(range: -0.25...0.25),
10 | colorRandomizer: @escaping MeshRandomizer.ColorRandomizer = MeshRandomizer.arrayBasedColorRandomizer(availableColors: (0...15).map({ _ in MeshRandomizer.randomColor() }))) {
11 | self.locationRandomizer = locationRandomizer
12 | self.turbulencyRandomizer = turbulencyRandomizer
13 | self.colorRandomizer = colorRandomizer
14 | }
15 |
16 | public typealias LocationRandomizer = (_ location: inout simd_float2, _ x: Int, _ y: Int, _ gridWidth: Int, _ gridHeight: Int) -> Void
17 | public typealias TangentRandomizer = (_ tangent: inout simd_float2, _ x: Int, _ y: Int, _ gridWidth: Int, _ gridHeight: Int) -> Void
18 | public typealias ColorRandomizer = (_ color: inout simd_float3, _ initialColor: simd_float3, _ x: Int, _ y: Int, _ gridWidth: Int, _ gridHeight: Int) -> Void
19 |
20 |
21 | public var locationRandomizer: LocationRandomizer
22 | public var turbulencyRandomizer: TangentRandomizer
23 | public var colorRandomizer: ColorRandomizer
24 |
25 |
26 | public static func randomColor() -> simd_float3 {
27 | .init(.random(in: 0...1), .random(in: 0...1), .random(in: 0...1))
28 | }
29 |
30 | /// Will randomly choose colors from the provided array
31 | /// - Parameter availableColors: List of colors that will be used for randomisation
32 | public static func arrayBasedColorRandomizer(availableColors: [simd_float3]) -> ColorRandomizer {
33 | assert(!availableColors.isEmpty, "Available colors can not be empty")
34 | return { color, _, _, _, _, _ in
35 | color = availableColors.randomElement()!
36 | }
37 | }
38 |
39 | /// Slightly changes Y location of the control points based on grid width and height.
40 | /// Grid is drawed from top to bottom, and because of that changing of X locations can create visual glitches
41 | public static func automaticallyRandomizeLocationYExceptTopAndBottomBasedOnGridSize() -> LocationRandomizer {
42 | return { location, x, y, gridWidth, gridHeight in
43 | let locationVariationRange = 1.2 * 1 / Float(gridHeight)
44 | if y != 0 && y != gridHeight - 1 {
45 | location.y += .random(in: -locationVariationRange...locationVariationRange)
46 | }
47 | }
48 | }
49 |
50 | /// Applies random values from range to each control point location. Avoids randomisation of control points on the edges
51 | /// - Parameter range: Range for randomisation
52 | public static func randomizeLocationExceptEdges(range: ClosedRange) -> LocationRandomizer {
53 | return { location, x, y, gridWidth, gridHeight in
54 | if x != 0 && x != gridWidth - 1 {
55 | location.x += .random(in: range)
56 | }
57 | if y != 0 && y != gridHeight - 1 {
58 | location.y += .random(in: range)
59 | }
60 | }
61 | }
62 |
63 | /// Applies random values from range for each control point tangents. Avoids randomisation of control points on the edges
64 | /// - Parameter range: Range for randomisation
65 | public static func randomizeTurbulencyExceptEdges(range: ClosedRange) -> TangentRandomizer {
66 | return { tangent, x, y, width, height in
67 | if (x != 0 && y != 0) && (x != width - 1 && y != height - 1) {
68 | tangent.x += .random(in: range)
69 | tangent.y += .random(in: range)
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Sources/MeshGradient/MetalMeshRenderer.swift:
--------------------------------------------------------------------------------
1 |
2 | import Foundation
3 | import Metal
4 | import MetalKit
5 | @_implementationOnly import MeshGradientCHeaders
6 |
7 | public final class MetalMeshRenderer: NSObject, MTKViewDelegate {
8 |
9 | var commandQueue: MTLCommandQueue?
10 | var bufferPool: MTLBufferPool?
11 |
12 | var computeNoiseFunction: MTLComputeNoiseFunction?
13 | var computeShuffleCoefficients: MTLComputeShuffleCoefficientsFunction?
14 | var computeHermitPatchMatrix: MTLComputeHermitPatchMatrixFunction?
15 | var computeMeshTrianglePrimitives: MTLComputeMeshTrianglePrimitivesFunction?
16 | var drawMesh: MTLDrawMeshTrianglesFunction?
17 |
18 | var viewportSize: vector_float2 = .zero
19 |
20 | var subdivisions: Int
21 | let meshDataProvider: MeshDataProvider
22 | let grainAlpha: Float
23 |
24 | public init(metalKitView mtkView: MTKView, meshDataProvider: MeshDataProvider, grainAlpha: Float, subdivisions: Int = 18) {
25 | self.subdivisions = subdivisions
26 | self.meshDataProvider = meshDataProvider
27 | self.grainAlpha = grainAlpha
28 |
29 | guard let device = mtkView.device,
30 | let defaultLibrary = try? device.makeDefaultLibrary(bundle: .module)
31 | else {
32 | assertionFailure()
33 | return
34 | }
35 |
36 |
37 | let bufferPool = MTLBufferPool(device: device)
38 |
39 | do {
40 | computeNoiseFunction = try .init(device: device, library: defaultLibrary)
41 | computeShuffleCoefficients = try .init(device: device, library: defaultLibrary, bufferPool: bufferPool)
42 | computeHermitPatchMatrix = try .init(device: device, library: defaultLibrary)
43 | computeMeshTrianglePrimitives = try .init(device: device, library: defaultLibrary)
44 | drawMesh = try .init(device: device, library: defaultLibrary, mtkView: mtkView)
45 |
46 | } catch {
47 | assertionFailure(error.localizedDescription)
48 | }
49 | commandQueue = device.makeCommandQueue()
50 | self.bufferPool = bufferPool
51 | }
52 |
53 | public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
54 | viewportSize.x = Float(size.width)
55 | viewportSize.y = Float(size.height)
56 | }
57 |
58 | private func calculateTriangles(grid: Grid, subdivisions: Int, commandBuffer: MTLCommandBuffer) -> (buffer: MTLBuffer, length: Int, elementsCount: Int)? {
59 | let resultVerticesSize = getResultVerticesSize(grid: grid, subdivisions: subdivisions)
60 |
61 | let resultTrianglesSize = (resultVerticesSize.width - 1) * (resultVerticesSize.height - 1) * 6
62 | let resultTrianglesBufferSize = MemoryLayout.stride * resultTrianglesSize
63 |
64 | guard let (triangleStripBuf, _, triangleStripCount) = calculateMeshTriangles(grid: grid, subdivisions: subdivisions, commandBuffer: commandBuffer),
65 | let resultTrianglesBuffer = bufferPool?[resultTrianglesBufferSize, .storageModePrivate],
66 | let computeMeshTrianglePrimitives = self.computeMeshTrianglePrimitives
67 | else { return nil }
68 | commandBuffer.addCompletedHandler { _ in
69 | self.bufferPool?[resultTrianglesBufferSize, .storageModePrivate] = resultTrianglesBuffer
70 | }
71 |
72 | computeMeshTrianglePrimitives.call(gridSize: resultVerticesSize,
73 | resultTrianglesBuffer: resultTrianglesBuffer,
74 | finalVertices: triangleStripBuf,
75 | finalVerticesSize: triangleStripCount,
76 | commandBuffer: commandBuffer)
77 | return (resultTrianglesBuffer, resultTrianglesBufferSize, resultTrianglesSize)
78 | }
79 |
80 | private func getResultVerticesSize(grid: Grid, subdivisions: Int) -> (width: Int, height: Int) {
81 | return (width: (grid.width - 1) * subdivisions, height: (grid.height - 1) * subdivisions)
82 | }
83 |
84 | private func calculateMeshTriangles(grid: Grid, subdivisions: Int, commandBuffer: MTLCommandBuffer) -> (buffer: MTLBuffer, length: Int, elementsCount: Int)? {
85 |
86 | let resultVerticesSize = getResultVerticesSize(grid: grid, subdivisions: subdivisions)
87 |
88 | let intermediateSize = (grid.width - 1) * (grid.height - 1)
89 | let intermediateBufferSize = intermediateSize * MemoryLayout.stride
90 |
91 | let finalVerticesSize = resultVerticesSize.width * resultVerticesSize.height
92 | let finalVerticesBufferSize = MemoryLayout.stride * finalVerticesSize
93 |
94 | guard let intermediateResultBuffer = bufferPool?[intermediateBufferSize, .storageModePrivate],
95 | let finalVerticesBuffer = bufferPool?[finalVerticesBufferSize, .storageModePrivate],
96 | let computeShuffleCoefficients = self.computeShuffleCoefficients,
97 | let computeHermitPatchMatrix = self.computeHermitPatchMatrix
98 |
99 | else {
100 | assertionFailure()
101 | return nil
102 | }
103 | commandBuffer.addCompletedHandler { _ in
104 | self.bufferPool?[intermediateBufferSize, .storageModePrivate] = intermediateResultBuffer
105 | self.bufferPool?[finalVerticesBufferSize, .storageModePrivate] = finalVerticesBuffer
106 | }
107 |
108 | commandBuffer.label = "Show Mesh Buffer"
109 |
110 | computeShuffleCoefficients.call(grid: grid,
111 | intermediateResultBuffer: intermediateResultBuffer,
112 | commandBuffer: commandBuffer)
113 |
114 |
115 | computeHermitPatchMatrix.call(subdivisions: subdivisions,
116 | resultBuffer: finalVerticesBuffer,
117 | intermediateResultBuffer: intermediateResultBuffer,
118 | gridWidth: resultVerticesSize.width,
119 | intermediateResultBufferSize: intermediateSize,
120 | commandBuffer: commandBuffer)
121 |
122 | return (finalVerticesBuffer, finalVerticesBufferSize, finalVerticesSize)
123 | }
124 |
125 | public func draw(in view: MTKView) {
126 | guard let commandQueue = commandQueue,
127 | let commandBuffer = commandQueue.makeCommandBuffer(),
128 | let computeNoise = self.computeNoiseFunction
129 | else { return }
130 | let grid = meshDataProvider.grid
131 |
132 | let noiseTexture = computeNoise.call(viewportSize: viewportSize,
133 | pixelFormat: view.colorPixelFormat,
134 | commandQueue: commandQueue,
135 | uniforms: .init(isSmooth: 1,
136 | color1: 94,
137 | color2: 168,
138 | color3: 147,
139 | noiseAlpha: grainAlpha))
140 |
141 | guard let (resultBuffer, _, resultElementsCount) = calculateTriangles(grid: grid, subdivisions: subdivisions, commandBuffer: commandBuffer),
142 | let drawMesh = self.drawMesh
143 | else { assertionFailure(); return }
144 |
145 | drawMesh.call(meshVertices: resultBuffer,
146 | noise: noiseTexture,
147 | meshVerticesCount: resultElementsCount,
148 | view: view,
149 | commandBuffer: commandBuffer,
150 | viewportSize: viewportSize)
151 |
152 | if let drawable = view.currentDrawable {
153 | commandBuffer.present(drawable)
154 | }
155 |
156 | commandBuffer.commit()
157 | }
158 |
159 | func unwrap(buffer: MTLBuffer, length: Int? = nil, elementsCount: Int) -> [Element] {
160 | let rawPointer = buffer.contents()
161 | let length = length ?? MemoryLayout.stride * elementsCount
162 | let typedPointer = rawPointer.bindMemory(to: Element.self, capacity: length)
163 | let bufferedPointer = UnsafeBufferPointer(start: typedPointer, count: length)
164 |
165 | var result: [Element] = []
166 | for i in 0.., animatorConfiguration: MeshAnimator.Configuration)
13 | case `static`(grid: Grid)
14 | }
15 |
16 | #if canImport(SwiftUI)
17 | import SwiftUI
18 |
19 | #if canImport(UIKit)
20 | import UIKit
21 |
22 | public struct LegacyMeshGradient: UIViewRepresentable {
23 |
24 | private let state: MeshGradientState
25 | private let subdivisions: Int
26 | private let grainAlpha: Float
27 |
28 | public init(initialGrid: Grid,
29 | animatorConfiguration: MeshAnimator.Configuration,
30 | grainAlpha: Float = MeshGradientDefaults.grainAlpha,
31 | subdivisions: Int = MeshGradientDefaults.subdivisions) {
32 | self.state = .animated(initial: initialGrid, animatorConfiguration: animatorConfiguration)
33 | self.grainAlpha = grainAlpha
34 | self.subdivisions = subdivisions
35 | }
36 |
37 | public init(grid: Grid,
38 | grainAlpha: Float = MeshGradientDefaults.grainAlpha,
39 | subdivisions: Int = MeshGradientDefaults.subdivisions) {
40 | self.state = .static(grid: grid)
41 | self.grainAlpha = grainAlpha
42 | self.subdivisions = subdivisions
43 | }
44 |
45 | public func makeUIView(context: Context) -> MTKView {
46 | let view = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
47 | context.coordinator.renderer = .init(metalKitView: view, meshDataProvider: createDataProvider(), grainAlpha: grainAlpha, subdivisions: subdivisions)
48 |
49 | switch state {
50 | case .animated(_, let configuration):
51 | view.isPaused = false
52 | view.enableSetNeedsDisplay = false
53 | view.preferredFramesPerSecond = configuration.framesPerSecond
54 | case .static:
55 | view.isPaused = true
56 | view.enableSetNeedsDisplay = true
57 | view.preferredFramesPerSecond = 60
58 | }
59 |
60 | view.delegate = context.coordinator.renderer
61 | return view
62 | }
63 |
64 | private func createDataProvider() -> MeshDataProvider {
65 | switch state {
66 | case .animated(let initial, let animatorConfiguration):
67 | return MeshAnimator(grid: initial, configuration: animatorConfiguration)
68 | case .static(let grid):
69 | return StaticMeshDataProvider(grid: grid)
70 | }
71 | }
72 |
73 | public func updateUIView(_ view: MTKView, context: Context) {
74 | switch state {
75 | case .animated(_, let animatorConfiguration):
76 | guard let animator = context.coordinator.renderer.meshDataProvider as? MeshAnimator else {
77 | fatalError("Incorrect mesh data provider type. Expected \(MeshAnimator.self), got \(type(of: context.coordinator.renderer.meshDataProvider))")
78 | }
79 | animator.configuration = animatorConfiguration
80 | animator.configuration.framesPerSecond = min(animatorConfiguration.framesPerSecond, view.preferredFramesPerSecond)
81 | case .static(let grid):
82 | guard let staticMesh = context.coordinator.renderer.meshDataProvider as? StaticMeshDataProvider else {
83 | fatalError("Incorrect mesh data provider type. Expected \(StaticMeshDataProvider.self), got \(type(of: context.coordinator.renderer.meshDataProvider))")
84 | }
85 | staticMesh.grid = grid
86 | view.setNeedsDisplay()
87 | }
88 | context.coordinator.renderer.mtkView(view, drawableSizeWillChange: view.drawableSize)
89 | context.coordinator.renderer.subdivisions = subdivisions
90 | }
91 |
92 | public func makeCoordinator() -> Coordinator {
93 | return .init()
94 | }
95 |
96 | public final class Coordinator {
97 | var renderer: MetalMeshRenderer!
98 | }
99 |
100 | }
101 |
102 | #elseif canImport(AppKit) // canImport(UIKit)
103 |
104 | import AppKit
105 |
106 | public struct MeshGradient: NSViewRepresentable {
107 |
108 | private let state: MeshGradientState
109 | private let subdivisions: Int
110 | private let grainAlpha: Float
111 |
112 | public init(initialGrid: Grid,
113 | animatorConfiguration: MeshAnimator.Configuration,
114 | grainAlpha: Float = MeshGradientDefaults.grainAlpha,
115 | subdivisions: Int = MeshGradientDefaults.subdivisions) {
116 | self.state = .animated(initial: initialGrid, animatorConfiguration: animatorConfiguration)
117 | self.grainAlpha = grainAlpha
118 | self.subdivisions = subdivisions
119 | }
120 |
121 | public init(grid: Grid,
122 | grainAlpha: Float = MeshGradientDefaults.grainAlpha,
123 | subdivisions: Int = MeshGradientDefaults.subdivisions) {
124 | self.state = .static(grid: grid)
125 | self.grainAlpha = grainAlpha
126 | self.subdivisions = subdivisions
127 | }
128 |
129 | public func makeNSView(context: Context) -> MTKView {
130 | let view = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
131 | context.coordinator.renderer = .init(metalKitView: view, meshDataProvider: createDataProvider(), grainAlpha: grainAlpha, subdivisions: subdivisions)
132 |
133 | switch state {
134 | case .animated(_, let configuration):
135 | view.isPaused = false
136 | view.enableSetNeedsDisplay = false
137 | view.preferredFramesPerSecond = configuration.framesPerSecond
138 | case .static:
139 | view.isPaused = true
140 | view.enableSetNeedsDisplay = true
141 | view.preferredFramesPerSecond = 60
142 | }
143 |
144 | view.delegate = context.coordinator.renderer
145 | return view
146 | }
147 |
148 | private func createDataProvider() -> MeshDataProvider {
149 | switch state {
150 | case .animated(let initial, let animatorConfiguration):
151 | return MeshAnimator(grid: initial, configuration: animatorConfiguration)
152 | case .static(let grid):
153 | return StaticMeshDataProvider(grid: grid)
154 | }
155 | }
156 |
157 | public func updateNSView(_ view: MTKView, context: Context) {
158 | switch state {
159 | case .animated(_, let animatorConfiguration):
160 | guard let animator = context.coordinator.renderer.meshDataProvider as? MeshAnimator else {
161 | fatalError("Incorrect mesh data provider type. Expected \(MeshAnimator.self), got \(type(of: context.coordinator.renderer.meshDataProvider))")
162 | }
163 | animator.configuration = animatorConfiguration
164 | animator.configuration.framesPerSecond = min(animatorConfiguration.framesPerSecond, view.preferredFramesPerSecond)
165 | case .static(let grid):
166 | guard let staticMesh = context.coordinator.renderer.meshDataProvider as? StaticMeshDataProvider else {
167 | fatalError("Incorrect mesh data provider type. Expected \(StaticMeshDataProvider.self), got \(type(of: context.coordinator.renderer.meshDataProvider))")
168 | }
169 | staticMesh.grid = grid
170 | view.setNeedsDisplay(view.bounds)
171 | }
172 | context.coordinator.renderer.mtkView(view, drawableSizeWillChange: view.drawableSize)
173 | context.coordinator.renderer.subdivisions = subdivisions
174 | }
175 |
176 | public func makeCoordinator() -> Coordinator {
177 | return .init()
178 | }
179 |
180 | public final class Coordinator {
181 | var renderer: MetalMeshRenderer!
182 | }
183 |
184 | }
185 | #endif // canImport(AppKit)
186 |
187 | #endif // canImport(SwiftUI)
188 |
--------------------------------------------------------------------------------
/Sources/MeshGradientCHeaders/PleasingSwiftPM.c:
--------------------------------------------------------------------------------
1 | //
2 | // PleasingSwiftPM.c
3 | //
4 | //
5 | // Created by Nikita Patskov on 08.06.2022.
6 | //
7 |
8 | #include
9 |
--------------------------------------------------------------------------------
/Sources/MeshGradientCHeaders/include/MetalMeshShaderTypes.h:
--------------------------------------------------------------------------------
1 | //
2 | // MetalMeshShaderTypes.h
3 | // ConvettiEmitter
4 | //
5 | // Created by Nikita Patskov on 03/06/2022.
6 | //
7 |
8 | #ifndef MetalMeshShaderTypes_h
9 | #define MetalMeshShaderTypes_h
10 |
11 | #include
12 | // Buffer index values shared between shader and C code to ensure Metal shader buffer inputs
13 | // match Metal API buffer set calls.
14 | typedef enum ComputeNoiseInputIndex
15 | {
16 | ComputeNoiseInputIndexOutputTexture = 0,
17 | ComputeNoiseInputIndexUniforms
18 | } AAPLVertexInputIndex;
19 |
20 | typedef enum ComputeMeshVertexInputIndex
21 | {
22 | ComputeMeshVertexInputIndexVertices = 0,
23 | ComputeMeshVertexInputIndexGridSize,
24 | ComputeMeshVertexInputIndexResult,
25 | ComputeMeshVertexInputCalculationSize,
26 | } ComputeMeshVertexInputIndex;
27 |
28 | typedef enum ComputeMeshFinalInputIndex
29 | {
30 | ComputeMeshFinalInputIndexVertices = 0,
31 | ComputeMeshFinalInputIndexWidth,
32 | ComputeMeshFinalInputIndexDepth,
33 | ComputeMeshFinalInputIndexSubdivisions,
34 | ComputeMeshFinalInputIndexResult,
35 | ComputeMeshFinalInputIndexCalculationSize,
36 |
37 | } ComputeMeshFinalInputIndex;
38 |
39 | typedef enum ComputeMeshTrianglesIndex
40 | {
41 | ComputeMeshTrianglesIndexVertices = 0,
42 | ComputeMeshTrianglesIndexWidth,
43 | ComputeMeshTrianglesIndexHeight,
44 | ComputeMeshTrianglesIndexResult,
45 | ComputeMeshTrianglesIndexCalculationSize,
46 | } ComputeMeshTrianglesIndex;
47 |
48 | typedef enum DrawMeshTextureIndex
49 | {
50 | DrawMeshTextureIndexBaseColor = 0,
51 | } DrawMeshTextureIndex;
52 |
53 | struct NoiseUniforms
54 | {
55 | int isSmooth;
56 | float color1;
57 | float color2;
58 | float color3;
59 | float noiseAlpha;
60 | };
61 |
62 | typedef struct
63 | {
64 | vector_float2 location;
65 | vector_float2 uTangent;
66 | vector_float2 vTangent;
67 |
68 | vector_float4 color;
69 |
70 | } MeshControlPoint;
71 |
72 | typedef struct
73 | {
74 | simd_float4x4 X;
75 | simd_float4x4 Y;
76 |
77 | simd_float4x4 R;
78 | simd_float4x4 G;
79 | simd_float4x4 B;
80 |
81 | unsigned int x;
82 | unsigned int y;
83 |
84 | } MeshIntermediateVertex;
85 |
86 | typedef struct
87 | {
88 | vector_float2 position;
89 | vector_float4 color;
90 | } MeshVertex;
91 |
92 |
93 | #endif /* MetalMeshShaderTypes_h */
94 |
--------------------------------------------------------------------------------