-
Notifications
You must be signed in to change notification settings - Fork 65
/
BlurPIX.swift
173 lines (149 loc) · 5.39 KB
/
BlurPIX.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//
// BlurPIX.swift
// PixelKit
//
// Created by Anton Heestand on 2018-08-02.
// Open Source - MIT License
//
import RenderKit
import Resolution
import CoreGraphics
import MetalKit
#if !os(tvOS) && !targetEnvironment(simulator)
// MPS does not support the iOS simulator.
import MetalPerformanceShaders
#endif
final public class BlurPIX: PIXSingleEffect, CustomRenderDelegate, PIXViewable {
override public var shaderName: String { return "effectSingleBlurPIX" }
// MARK: - Public Properties
/// Gaussian blur is the most performant, tho it's not supported in the simulator.
public enum BlurStyle: String, Enumable {
case gaussian
case box
case angle
case zoom
case random
static let `default`: BlurStyle = {
#if !os(tvOS) && !targetEnvironment(simulator)
return .gaussian
#else
return .box
#endif
}()
public var index: Int {
switch self {
case .gaussian: return 0
case .box: return 1
case .angle: return 2
case .zoom: return 3
case .random: return 4
}
}
public var typeName: String { rawValue }
public var name: String {
switch self {
case .gaussian: return "Guassian"
case .box: return "Box"
case .angle: return "Angle"
case .zoom: return "Zoom"
case .random: return "Random"
}
}
}
@LiveEnum("style") public var style: BlurStyle = .default
/// radius is relative. default at 0.5
///
/// 1.0 at 4K is max, tho at lower resolutions you can go beyond 1.0
@LiveFloat("radius", increment: 0.125) public var radius: CGFloat = 0.5
@LiveEnum("quality") public var quality: SampleQualityMode = .mid
@LiveFloat("angle", range: -0.5...0.5) public var angle: CGFloat = 0.0
@LivePoint("position") public var position: CGPoint = .zero
// MARK: - Property Helpers
public override var liveList: [LiveWrap] {
[_style, _radius, _quality, _angle, _position]
}
var relRadius: CGFloat {
let radius = radius
let relRes: Resolution = ._4K
let res: Resolution = finalResolution
let relHeight = res.height / relRes.height
let relRadius = radius * relHeight
let maxRadius: CGFloat = 32 * 10
let mappedRadius = relRadius * maxRadius
return mappedRadius
}
public override var uniforms: [CGFloat] {
return [CGFloat(style.index), relRadius, CGFloat(quality.rawValue), angle, position.x, position.y]
}
override public var shaderNeedsResolution: Bool { return true }
// MARK: - Life Cycle
public required init() {
style = .default
super.init(name: "Blur", typeName: "pix-effect-single-blur")
extend = .hold
customRenderDelegate = self
}
public required init(from decoder: Decoder) throws {
#if !os(tvOS) && !targetEnvironment(simulator)
style = .gaussian
#else
style = .box
#endif
try super.init(from: decoder)
extend = .hold
customRenderDelegate = self
}
// MARK: Guassian
override public func render() {
#if !os(tvOS) && !targetEnvironment(simulator)
customRenderActive = style == .gaussian
#endif
super.render()
}
public func customRender(_ texture: MTLTexture, with commandBuffer: MTLCommandBuffer) -> MTLTexture? {
#if !os(tvOS) && !targetEnvironment(simulator)
return gaussianBlur(texture, with: commandBuffer)
#else
return nil
#endif
}
#if !os(tvOS) && !targetEnvironment(simulator)
func gaussianBlur(_ texture: MTLTexture, with commandBuffer: MTLCommandBuffer) -> MTLTexture? {
if #available(OSX 10.13, *) {
guard let blurTexture = try? Texture.emptyTexture(size: CGSize(width: texture.width, height: texture.height), bits: pixelKit.render.bits, on: pixelKit.render.metalDevice, write: true) else {
pixelKit.logger.log(node: self, .error, .generator, "Guassian Blur: Make texture faild.")
return nil
}
let gaussianBlurKernel = MPSImageGaussianBlur(device: pixelKit.render.metalDevice, sigma: Float(relRadius))
gaussianBlurKernel.edgeMode = extend.mps!
gaussianBlurKernel.encode(commandBuffer: commandBuffer, sourceTexture: texture, destinationTexture: blurTexture)
return blurTexture
} else {
return nil
}
}
#endif
}
public extension NODEOut {
func pixBlur(_ radius: CGFloat) -> BlurPIX {
let blurPix = BlurPIX()
blurPix.name = ":blur:"
blurPix.input = self as? PIX & NODEOut
blurPix.radius = radius
return blurPix
}
func pixZoomBlur(_ radius: CGFloat) -> BlurPIX {
let blurPix = BlurPIX()
blurPix.name = ":zoom-blur:"
blurPix.style = .zoom
blurPix.quality = .epic
blurPix.input = self as? PIX & NODEOut
blurPix.radius = radius
return blurPix
}
func pixBloom(radius: CGFloat, amount: CGFloat) -> CrossPIX {
let pix = self as? PIX & NODEOut
let bloomPix = (pix!.pixBlur(radius) + pix!) / 2
return pixCross(pix!, bloomPix, at: amount)
}
}