diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseFilterRender.swift index 3d489ab..ae7a4eb 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseFilterRender.swift +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseFilterRender.swift @@ -17,5 +17,5 @@ open class BaseFilterRender { fatalError("draw method must be overriden") } open func setMetalInfo(commandQueue: any MTLCommandQueue, context: CIContext) { } - open func initMetal(width: Int, height: Int, device: MTLDevice) { } + open func initMetal(device: MTLDevice) { } } diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseShaderFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseShaderFilterRender.swift index 9522a40..fbb0fa1 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseShaderFilterRender.swift +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseShaderFilterRender.swift @@ -14,18 +14,21 @@ open class BaseShaderFilterRender: BaseFilterRender { private var context: CIContext? = nil private var commandQueue: MTLCommandQueue? = nil + private var device: MTLDevice? = nil private let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() private var pipelineState: MTLRenderPipelineState? = nil private var inputTexture: MTLTexture? = nil private var outputTexturePreview: MTLTexture? = nil private var outputTextureOutput: MTLTexture? = nil + public private(set) var resolution = SIMD2(640, 480) public override func setMetalInfo(commandQueue: any MTLCommandQueue, context: CIContext) { self.commandQueue = commandQueue self.context = context } - public override func initMetal(width: Int, height: Int, device: MTLDevice) { + public override func initMetal(device: MTLDevice) { + self.device = device do { let library = try device.makeLibrary(source: initMetalFilter(), options: nil) guard let vertexFunction = library.makeFunction(name: "vertexFilter"), @@ -37,22 +40,29 @@ open class BaseShaderFilterRender: BaseFilterRender { descriptor.fragmentFunction = fragmentFunction descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm self.pipelineState = try device.makeRenderPipelineState(descriptor: descriptor) - self.inputTexture = MetalUtils.getTexture(device: device, texture: &inputTexture, width: width, height: height) - self.outputTexturePreview = MetalUtils.getTexture(device: device, texture: &outputTexturePreview, width: width, height: height) - self.outputTextureOutput = MetalUtils.getTexture(device: device, texture: &outputTextureOutput, width: width, height: height) } catch { fatalError("Shader compilation failed: \(error)") } } public override func draw(image: CIImage, orientation: CGImagePropertyOrientation, isPreview: Bool) -> CIImage { - let outputTexture = if isPreview { outputTexturePreview } else { outputTextureOutput } + let width = Int(image.extent.width) + let height = Int(image.extent.height) + if width <= 0 || height <= 0 { return image } guard let pipelineState = pipelineState, + let device = device, let commandBuffer = commandQueue?.makeCommandBuffer(), - let context = context, - let input = inputTexture, - let output = outputTexture else { return image } - + let context = context else { return image } + resolution = SIMD2(Float(width), Float(height)) + let output: MTLTexture? + if isPreview { + output = MetalUtils.getTexture(device: device, texture: &outputTexturePreview, width: width, height: height) + } else { + output = MetalUtils.getTexture(device: device, texture: &outputTextureOutput, width: width, height: height) + } + guard let input = MetalUtils.getTexture(device: device, texture: &inputTexture, width: width, height: height), + let output = output else { return image } + context.render(image, to: input, commandBuffer: commandBuffer, bounds: image.extent, colorSpace: colorSpace) let descriptor = MTLRenderPassDescriptor() diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/MainRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/MainRender.swift index e66b768..eb26770 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/MainRender.swift +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/MainRender.swift @@ -34,7 +34,7 @@ public class MainRender { private func setFilter(position: Int, baseFilterRender: BaseFilterRender) { baseFilterRender.setMetalInfo(commandQueue: commandQueue, context: context) - baseFilterRender.initMetal(width: width, height: height, device: device) + baseFilterRender.initMetal(device: device) if filterRenders.isEmpty && position == 0 { addFilter(baseFilterRender: baseFilterRender) } else { @@ -45,13 +45,13 @@ public class MainRender { private func addFilter(baseFilterRender: BaseFilterRender) { baseFilterRender.setMetalInfo(commandQueue: commandQueue, context: context) - baseFilterRender.initMetal(width: width, height: height, device: device) + baseFilterRender.initMetal(device: device) filterRenders.append(baseFilterRender) } private func addFilter(position: Int, baseFilterRender: BaseFilterRender) { baseFilterRender.setMetalInfo(commandQueue: commandQueue, context: context) - baseFilterRender.initMetal(width: width, height: height, device: device) + baseFilterRender.initMetal(device: device) filterRenders.insert(baseFilterRender, at: position) } @@ -98,8 +98,22 @@ public class MainRender { let validFilters = filterRenders.filter { if isPreview { $0.renderMode != .OUTPUT } else { $0.renderMode != .PREVIEW } } + if validFilters.isEmpty { return } + var filteredImage = image.oriented(orientation) validFilters.forEach { filter in - image = filter.draw(image: image, orientation: orientation, isPreview: isPreview) + filteredImage = filter.draw(image: filteredImage, orientation: orientation, isPreview: isPreview) + } + image = filteredImage.oriented(inverseOrientation(orientation)) + } + + private func inverseOrientation(_ orientation: CGImagePropertyOrientation) -> CGImagePropertyOrientation { + switch orientation { + case .left: + return .right + case .right: + return .left + default: + return orientation } } diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/AnalogTVFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/AnalogTVFilterRender.swift new file mode 100644 index 0000000..ca9525c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/AnalogTVFilterRender.swift @@ -0,0 +1,25 @@ +// +// AnalogTVFilterRender.swift +// RootEncoder +// +// Ported from Android AnalogTVFilterRender +// + +import Foundation +import Metal + +public class AnalogTVFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "AnalogTvFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + var res = resolution + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BasicDeformationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BasicDeformationFilterRender.swift new file mode 100644 index 0000000..366139c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BasicDeformationFilterRender.swift @@ -0,0 +1,23 @@ +// +// BasicDeformationFilterRender.swift +// RootEncoder +// +// Ported from Android BasicDeformationFilterRender +// + +import Foundation +import Metal + +public class BasicDeformationFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "BasicDeformationFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BeautyFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BeautyFilterRender.swift new file mode 100644 index 0000000..26b6f23 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BeautyFilterRender.swift @@ -0,0 +1,21 @@ +// +// BeautyFilterRender.swift +// RootEncoder +// +// Ported from Android BeautyFilterRender +// + +import Foundation +import Metal + +public class BeautyFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "BeautyFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = SIMD2(2 / resolution.x, 2 / resolution.y) + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlackFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlackFilterRender.swift new file mode 100644 index 0000000..09f5815 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlackFilterRender.swift @@ -0,0 +1,16 @@ +// +// BlackFilterRender.swift +// RootEncoder +// +// Ported from Android BlackFilterRender +// + +import Foundation +import Metal + +public class BlackFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "BlackFilter") + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlurFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlurFilterRender.swift new file mode 100644 index 0000000..5b1e3ae --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BlurFilterRender.swift @@ -0,0 +1,24 @@ +// +// BlurFilterRender.swift +// RootEncoder +// +// Ported from Android BlurFilterRender +// + +import Foundation +import Metal + +public class BlurFilterRender: BaseShaderFilterRender { + + public var blur: Float = 10 + public var radius: Float = 0.03 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "BlurFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&blur, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&radius, length: MemoryLayout.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BrightnessFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BrightnessFilterRender.swift new file mode 100644 index 0000000..d54a98c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/BrightnessFilterRender.swift @@ -0,0 +1,22 @@ +// +// BrightnessFilterRender.swift +// RootEncoder +// +// Ported from Android BrightnessFilterRender +// + +import Foundation +import Metal + +public class BrightnessFilterRender: BaseShaderFilterRender { + + public var brightness: Float = 0.5 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "BrightnessFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&brightness, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CartoonFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CartoonFilterRender.swift new file mode 100644 index 0000000..a3b08d7 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CartoonFilterRender.swift @@ -0,0 +1,22 @@ +// +// CartoonFilterRender.swift +// RootEncoder +// +// Ported from Android CartoonFilterRender +// + +import Foundation +import Metal + +public class CartoonFilterRender: BaseShaderFilterRender { + + public var cartoon: Float = 0.007 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "CartoonFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&cartoon, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ChromaticAberrationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ChromaticAberrationFilterRender.swift new file mode 100644 index 0000000..412532b --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ChromaticAberrationFilterRender.swift @@ -0,0 +1,23 @@ +// +// ChromaticAberrationFilterRender.swift +// RootEncoder +// +// Ported from Android ChromaticAberrationFilterRender +// + +import Foundation +import Metal + +public class ChromaticAberrationFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "ChromaticAberrationFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CircleFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CircleFilterRender.swift new file mode 100644 index 0000000..c20a1ea --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CircleFilterRender.swift @@ -0,0 +1,27 @@ +// +// CircleFilterRender.swift +// RootEncoder +// +// Ported from Android CircleFilterRender +// + +import Foundation +import Metal + +public class CircleFilterRender: BaseShaderFilterRender { + + public var radius: Float = 0.5 + //center of the circle in percent (0.0 to 1.0) + public var center = SIMD2(0.5, 0.5) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "CircleFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&radius, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(¢er, length: MemoryLayout>.size, index: 1) + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 2) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ColorFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ColorFilterRender.swift new file mode 100644 index 0000000..a903114 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ColorFilterRender.swift @@ -0,0 +1,27 @@ +// +// ColorFilterRender.swift +// RootEncoder +// +// Ported from Android ColorFilterRender +// + +import Foundation +import Metal + +public class ColorFilterRender: BaseShaderFilterRender { + + //color in percent (0.0 to 1.0) + public var color = SIMD3(0, 0, 1) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "ColorFilter") + } + + public func setRGBColor(r: Int, g: Int, b: Int) { + color = SIMD3(Float(r) / 255, Float(g) / 255, Float(b) / 255) + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&color, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ContrastFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ContrastFilterRender.swift new file mode 100644 index 0000000..0b4efd7 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ContrastFilterRender.swift @@ -0,0 +1,22 @@ +// +// ContrastFilterRender.swift +// RootEncoder +// +// Ported from Android ContrastFilterRender +// + +import Foundation +import Metal + +public class ContrastFilterRender: BaseShaderFilterRender { + + public var contrast: Float = 0.5 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "ContrastFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&contrast, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CropFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CropFilterRender.swift new file mode 100644 index 0000000..b65affb --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/CropFilterRender.swift @@ -0,0 +1,29 @@ +// +// CropFilterRender.swift +// RootEncoder +// +// Ported from Android CropFilterRender +// + +import Foundation +import Metal + +public class CropFilterRender: BaseShaderFilterRender { + + private var area = SIMD4(0, 0, 1, 1) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "CropFilter") + } + + /** + Set crop area in percentage (0 to 100). Starting from top left corner. + */ + public func setCropArea(offsetX: Float, offsetY: Float, width: Float, height: Float) { + area = SIMD4(offsetX / 100, offsetY / 100, width / 100, height / 100) + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&area, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DistortedTvFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DistortedTvFilterRender.swift new file mode 100644 index 0000000..20ce3c8 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DistortedTvFilterRender.swift @@ -0,0 +1,23 @@ +// +// DistortedTvFilterRender.swift +// RootEncoder +// +// Ported from Android DistortedTvFilterRender +// + +import Foundation +import Metal + +public class DistortedTvFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "DistortedTvFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DuotoneFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DuotoneFilterRender.swift new file mode 100644 index 0000000..4f9b204 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/DuotoneFilterRender.swift @@ -0,0 +1,30 @@ +// +// DuotoneFilterRender.swift +// RootEncoder +// +// Ported from Android DuotoneFilterRender +// + +import Foundation +import Metal + +public class DuotoneFilterRender: BaseShaderFilterRender { + + //colors in percent (0.0 to 1.0) + public var color = SIMD3(0, 1, 0) + public var color2 = SIMD3(0, 0, 1) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "DuotoneFilter") + } + + public func setRGBColor(r: Int, g: Int, b: Int, r2: Int, g2: Int, b2: Int) { + color = SIMD3(Float(r) / 255, Float(g) / 255, Float(b) / 255) + color2 = SIMD3(Float(r2) / 255, Float(g2) / 255, Float(b2) / 255) + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&color, length: MemoryLayout>.size, index: 0) + encoder.setFragmentBytes(&color2, length: MemoryLayout>.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EarlyBirdFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EarlyBirdFilterRender.swift new file mode 100644 index 0000000..22a6374 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EarlyBirdFilterRender.swift @@ -0,0 +1,21 @@ +// +// EarlyBirdFilterRender.swift +// RootEncoder +// +// Ported from Android EarlyBirdFilterRender +// + +import Foundation +import Metal + +public class EarlyBirdFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "EarlyBirdFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EdgeDetectionFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EdgeDetectionFilterRender.swift new file mode 100644 index 0000000..389f55b --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/EdgeDetectionFilterRender.swift @@ -0,0 +1,16 @@ +// +// EdgeDetectionFilterRender.swift +// RootEncoder +// +// Ported from Android EdgeDetectionFilterRender +// + +import Foundation +import Metal + +public class EdgeDetectionFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "EdgeDetectionFilter") + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ExposureFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ExposureFilterRender.swift new file mode 100644 index 0000000..80ec272 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ExposureFilterRender.swift @@ -0,0 +1,22 @@ +// +// ExposureFilterRender.swift +// RootEncoder +// +// Ported from Android ExposureFilterRender +// + +import Foundation +import Metal + +public class ExposureFilterRender: BaseShaderFilterRender { + + public var exposure: Float = 0.5 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "ExposureFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&exposure, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/FireFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/FireFilterRender.swift new file mode 100644 index 0000000..8bcf028 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/FireFilterRender.swift @@ -0,0 +1,21 @@ +// +// FireFilterRender.swift +// RootEncoder +// +// Ported from Android FireFilterRender +// + +import Foundation +import Metal + +public class FireFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "FireFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GammaFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GammaFilterRender.swift new file mode 100644 index 0000000..da4b707 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GammaFilterRender.swift @@ -0,0 +1,22 @@ +// +// GammaFilterRender.swift +// RootEncoder +// +// Ported from Android GammaFilterRender +// + +import Foundation +import Metal + +public class GammaFilterRender: BaseShaderFilterRender { + + public var gamma: Float = 0.5 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "GammaFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&gamma, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GlitchFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GlitchFilterRender.swift new file mode 100644 index 0000000..fbab7df --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GlitchFilterRender.swift @@ -0,0 +1,23 @@ +// +// GlitchFilterRender.swift +// RootEncoder +// +// Ported from Android GlitchFilterRender +// + +import Foundation +import Metal + +public class GlitchFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "GlitchFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GreyScaleFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GreyScaleFilterRender.swift index 41fc642..c5c4e81 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GreyScaleFilterRender.swift +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GreyScaleFilterRender.swift @@ -6,16 +6,11 @@ // import Foundation -import CoreImage +import Metal -public class GreyScaleFilterRender: BaseFilterRender { - - private let filter = CIFilter(name: "CIColorMonochrome") - - public override func draw(image: CIImage, orientation: CGImagePropertyOrientation, isPreview: Bool) -> CIImage { - filter?.setValue(image, forKey: kCIInputImageKey) - filter?.setValue(CIColor(red: 0.75, green: 0.75, blue: 0.75), forKey: kCIInputColorKey) - filter?.setValue(1.0, forKey: kCIInputIntensityKey) - return filter?.outputImage ?? image +public class GreyScaleFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "GreyScaleFilter") } } diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/HalftoneLinesFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/HalftoneLinesFilterRender.swift new file mode 100644 index 0000000..9acaf95 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/HalftoneLinesFilterRender.swift @@ -0,0 +1,33 @@ +// +// HalftoneLinesFilterRender.swift +// RootEncoder +// +// Ported from Android HalftoneLinesFilterRender +// + +import Foundation +import Metal + +public class HalftoneLinesFilterRender: BaseShaderFilterRender { + + //mode from 1 to 7 + public var mode: Float = 1 + public var rows: Float = 40 + public var rotation: Float = 0 + public var antialias: Float = 0.2 + public var sampleDist = SIMD2(2, 2) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "HalftoneLinesFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + encoder.setFragmentBytes(&mode, length: MemoryLayout.size, index: 1) + encoder.setFragmentBytes(&rows, length: MemoryLayout.size, index: 2) + encoder.setFragmentBytes(&rotation, length: MemoryLayout.size, index: 3) + encoder.setFragmentBytes(&antialias, length: MemoryLayout.size, index: 4) + encoder.setFragmentBytes(&sampleDist, length: MemoryLayout>.size, index: 5) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/Image70sFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/Image70sFilterRender.swift new file mode 100644 index 0000000..8468cde --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/Image70sFilterRender.swift @@ -0,0 +1,16 @@ +// +// Image70sFilterRender.swift +// RootEncoder +// +// Ported from Android Image70sFilterRender +// + +import Foundation +import Metal + +public class Image70sFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "Image70sFilter") + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/LamoishFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/LamoishFilterRender.swift new file mode 100644 index 0000000..e49360c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/LamoishFilterRender.swift @@ -0,0 +1,16 @@ +// +// LamoishFilterRender.swift +// RootEncoder +// +// Ported from Android LamoishFilterRender +// + +import Foundation +import Metal + +public class LamoishFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "LamoishFilter") + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/MoneyFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/MoneyFilterRender.swift new file mode 100644 index 0000000..2900b78 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/MoneyFilterRender.swift @@ -0,0 +1,21 @@ +// +// MoneyFilterRender.swift +// RootEncoder +// +// Ported from Android MoneyFilterRender +// + +import Foundation +import Metal + +public class MoneyFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "MoneyFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NegativeFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NegativeFilterRender.swift new file mode 100644 index 0000000..42cede9 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NegativeFilterRender.swift @@ -0,0 +1,16 @@ +// +// NegativeFilterRender.swift +// RootEncoder +// +// Ported from Android NegativeFilterRender +// + +import Foundation +import Metal + +public class NegativeFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "NegativeFilter") + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NoiseFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NoiseFilterRender.swift new file mode 100644 index 0000000..b01b298 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/NoiseFilterRender.swift @@ -0,0 +1,25 @@ +// +// NoiseFilterRender.swift +// RootEncoder +// +// Ported from Android NoiseFilterRender +// + +import Foundation +import Metal + +public class NoiseFilterRender: BaseShaderFilterRender { + + public var strength: Float = 16 + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "NoiseFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&strength, length: MemoryLayout.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PixelatedFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PixelatedFilterRender.swift new file mode 100644 index 0000000..66dfba3 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PixelatedFilterRender.swift @@ -0,0 +1,22 @@ +// +// PixelatedFilterRender.swift +// RootEncoder +// +// Ported from Android PixelatedFilterRender +// + +import Foundation +import Metal + +public class PixelatedFilterRender: BaseShaderFilterRender { + + public var pixelated: Float = 0.01 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "PixelatedFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&pixelated, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PolygonizationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PolygonizationFilterRender.swift new file mode 100644 index 0000000..56962db --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/PolygonizationFilterRender.swift @@ -0,0 +1,21 @@ +// +// PolygonizationFilterRender.swift +// RootEncoder +// +// Ported from Android PolygonizationFilterRender +// + +import Foundation +import Metal + +public class PolygonizationFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "PolygonizationFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RGBSaturationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RGBSaturationFilterRender.swift new file mode 100644 index 0000000..f918e76 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RGBSaturationFilterRender.swift @@ -0,0 +1,27 @@ +// +// RGBSaturationFilterRender.swift +// RootEncoder +// +// Ported from Android RGBSaturationFilterRender +// + +import Foundation +import Metal + +public class RGBSaturationFilterRender: BaseShaderFilterRender { + + //saturation of each color in percent (0.0 to 1.0) + public var saturation = SIMD3(1, 1, 1) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "RgbSaturationFilter") + } + + public func setRGBSaturation(r: Float, g: Float, b: Float) { + saturation = SIMD3(r, g, b) + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&saturation, length: MemoryLayout>.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RainbowFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RainbowFilterRender.swift new file mode 100644 index 0000000..08ed86a --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RainbowFilterRender.swift @@ -0,0 +1,23 @@ +// +// RainbowFilterRender.swift +// RootEncoder +// +// Ported from Android RainbowFilterRender +// + +import Foundation +import Metal + +public class RainbowFilterRender: BaseShaderFilterRender { + + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "RainbowFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RippleFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RippleFilterRender.swift new file mode 100644 index 0000000..1c9bba8 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RippleFilterRender.swift @@ -0,0 +1,27 @@ +// +// RippleFilterRender.swift +// RootEncoder +// +// Ported from Android RippleFilterRender +// + +import Foundation +import Metal + +public class RippleFilterRender: BaseShaderFilterRender { + + public var speed: Float = 15 + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "RippleFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + encoder.setFragmentBytes(&speed, length: MemoryLayout.size, index: 1) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 2) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RotationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RotationFilterRender.swift new file mode 100644 index 0000000..095eb94 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/RotationFilterRender.swift @@ -0,0 +1,28 @@ +// +// RotationFilterRender.swift +// RootEncoder +// +// Ported from Android RotationFilterRender +// + +import Foundation +import Metal + +public class RotationFilterRender: BaseShaderFilterRender { + + //rotation in degrees + public var rotation: Int = 0 + public var horizontalFlip = false + public var verticalFlip = false + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "RotationFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var radians = Float(rotation) * Float.pi / 180 + var flips = SIMD2(horizontalFlip ? 1 : 0, verticalFlip ? 1 : 0) + encoder.setFragmentBytes(&radians, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&flips, length: MemoryLayout>.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SaturationFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SaturationFilterRender.swift new file mode 100644 index 0000000..732c9e5 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SaturationFilterRender.swift @@ -0,0 +1,40 @@ +// +// SaturationFilterRender.swift +// RootEncoder +// +// Ported from Android SaturationFilterRender +// + +import Foundation +import Metal + +public class SaturationFilterRender: BaseShaderFilterRender { + + private var saturation: Float = -0.5 + private var shift: Float = 1.0 / 255.0 + private var weights = SIMD3(0.3086, 0.6094, 0.0820) + private var exponents = SIMD3(0, 0, 0) + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "SaturationFilter") + } + + /** + @param saturation between -1.0 and 1.0, 0.0 means no change while -1.0 indicates full desaturation (grayscale) + */ + public func setSaturation(saturation: Float) { + if saturation > 0.0 { + exponents = SIMD3((0.9 * saturation) + 1.0, (2.1 * saturation) + 1.0, (2.7 * saturation) + 1.0) + self.saturation = saturation + } else { + self.saturation = saturation + 1.0 + } + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&shift, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&weights, length: MemoryLayout>.size, index: 1) + encoder.setFragmentBytes(&exponents, length: MemoryLayout>.size, index: 2) + encoder.setFragmentBytes(&saturation, length: MemoryLayout.size, index: 3) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SepiaFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SepiaFilterRender.swift index 5b8f8cc..6aaa9df 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SepiaFilterRender.swift +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SepiaFilterRender.swift @@ -6,15 +6,11 @@ // import Foundation -import CoreImage +import Metal -public class SepiaFilterRender: BaseFilterRender { - - private let filter = CIFilter(name: "CISepiaTone") - - public override func draw(image: CIImage, orientation: CGImagePropertyOrientation, isPreview: Bool) -> CIImage { - filter?.setValue(image, forKey: kCIInputImageKey) - filter?.setValue(1.0, forKey: kCIInputIntensityKey) - return filter?.outputImage ?? image +public class SepiaFilterRender: BaseShaderFilterRender { + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "SepiaFilter") } } diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SharpnessFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SharpnessFilterRender.swift new file mode 100644 index 0000000..33cbb12 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SharpnessFilterRender.swift @@ -0,0 +1,24 @@ +// +// SharpnessFilterRender.swift +// RootEncoder +// +// Ported from Android SharpnessFilterRender +// + +import Foundation +import Metal + +public class SharpnessFilterRender: BaseShaderFilterRender { + + public var sharpness: Float = 16 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "SharpnessFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var res = resolution + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 0) + encoder.setFragmentBytes(&sharpness, length: MemoryLayout.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SnowFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SnowFilterRender.swift new file mode 100644 index 0000000..045c9a3 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SnowFilterRender.swift @@ -0,0 +1,31 @@ +// +// SnowFilterRender.swift +// RootEncoder +// +// Ported from Android SnowFilterRender +// + +import Foundation +import Metal + +public class SnowFilterRender: BaseShaderFilterRender { + + public var layers: Float = 5 + public var depth: Float = 0.5 + public var snowWidth: Float = 0.6 + public var speed: Float = 0.6 + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "SnowFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&layers, length: MemoryLayout.size, index: 1) + encoder.setFragmentBytes(&depth, length: MemoryLayout.size, index: 2) + encoder.setFragmentBytes(&snowWidth, length: MemoryLayout.size, index: 3) + encoder.setFragmentBytes(&speed, length: MemoryLayout.size, index: 4) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SwirlFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SwirlFilterRender.swift new file mode 100644 index 0000000..ea8035b --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SwirlFilterRender.swift @@ -0,0 +1,49 @@ +// +// SwirlFilterRender.swift +// RootEncoder +// +// Ported from Android SwirlFilterRender +// + +import Foundation +import Metal + +public class SwirlFilterRender: BaseShaderFilterRender { + + public var radius: Float = 0.2 + //center of the effect in percent (0.0 to 1.0) + public var center = SIMD2(0.5, 0.5) + private var time: Float = 0 + private var lastTime = Date() + private var isIncrement = true + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "SwirlFilter") + } + + private func getTime() -> Float { + let now = Date() + let interval = Float(now.timeIntervalSince(lastTime)) + lastTime = now + if isIncrement { + time += interval + } else { + time -= interval + } + if time > 2 { + isIncrement = false + } else if time < -2 { + isIncrement = true + } + return time + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = getTime() + var res = resolution + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&res, length: MemoryLayout>.size, index: 1) + encoder.setFragmentBytes(&radius, length: MemoryLayout.size, index: 2) + encoder.setFragmentBytes(¢er, length: MemoryLayout>.size, index: 3) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/TemperatureFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/TemperatureFilterRender.swift new file mode 100644 index 0000000..b04f648 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/TemperatureFilterRender.swift @@ -0,0 +1,23 @@ +// +// TemperatureFilterRender.swift +// RootEncoder +// +// Ported from Android TemperatureFilterRender +// + +import Foundation +import Metal + +public class TemperatureFilterRender: BaseShaderFilterRender { + + //temperature between -1.0 (cold) and 1.0 (warm) + public var temperature: Float = 0.8 + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "TemperatureFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + encoder.setFragmentBytes(&temperature, length: MemoryLayout.size, index: 0) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ZebraFilterRender.swift b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ZebraFilterRender.swift new file mode 100644 index 0000000..cd521e5 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/ZebraFilterRender.swift @@ -0,0 +1,25 @@ +// +// ZebraFilterRender.swift +// RootEncoder +// +// Ported from Android ZebraFilterRender +// + +import Foundation +import Metal + +public class ZebraFilterRender: BaseShaderFilterRender { + + public var levels: Float = 8 + private let startTime = Date() + + public override func initMetalFilter() -> String { + return MetalUtils.readShader(name: "FilterVertex") + MetalUtils.readShader(name: "ZebraFilter") + } + + public override func drawFilter(encoder: MTLRenderCommandEncoder) { + var time = Float(-startTime.timeIntervalSinceNow) + encoder.setFragmentBytes(&time, length: MemoryLayout.size, index: 0) + encoder.setFragmentBytes(&levels, length: MemoryLayout.size, index: 1) + } +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/AnalogTvFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/AnalogTvFilter.metal new file mode 100644 index 0000000..a3d2389 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/AnalogTvFilter.metal @@ -0,0 +1,41 @@ +// +// AnalogTvFilter.metal +// RootEncoder +// +// Ported from Android analog_tv_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float analogTvRand(float2 co) { + return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453); +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]], + constant float2 &uResolution [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 uv = data.uv; + uv -= 0.5; + + float3 col = tex.sample(s, uv + 0.5).rgb; + + float2 ruv = uv; + ruv.x += 0.02; + col.rgb += tex.sample(s, ruv + 0.5).rgb * 0.1; + + col += analogTvRand(fract(floor((ruv + uTime) * uResolution.y) * 0.7)) * 0.2; + + col *= clamp(fract(uv.y * 100.0 + uTime * 8.0), 0.8, 1.0); + + float bf = fract(uv.y * 3.0 + uTime * 26.0); + float ff = min(bf, 1.0 - bf) + 0.35; + col *= clamp(ff, 0.5, 0.75) + 0.75; + + col *= (sin(uTime * 120.0) * 0.5 + 0.5) * 0.1 + 0.9; + + col *= smoothstep(-0.51, -0.50, uv.x) * smoothstep(0.51, 0.50, uv.x); + col *= smoothstep(-0.51, -0.50, uv.y) * smoothstep(0.51, 0.50, uv.y); + + return float4(col, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BasicDeformationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BasicDeformationFilter.metal new file mode 100644 index 0000000..ca8b96f --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BasicDeformationFilter.metal @@ -0,0 +1,15 @@ +// +// BasicDeformationFilter.metal +// RootEncoder +// +// Ported from Android basic_deformation_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float waveu = sin((data.uv.y + uTime) * 20.0) * 0.5 * 0.05 * 0.3; + return tex.sample(s, data.uv + float2(waveu, 0.0)); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BeautyFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BeautyFilter.metal new file mode 100644 index 0000000..24882ea --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BeautyFilter.metal @@ -0,0 +1,95 @@ +// +// BeautyFilter.metal +// RootEncoder +// +// Ported from Android beauty_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float beautyHardLight(float color) { + if (color <= 0.5) { + color = color * color * 2.0; + } else { + color = 1.0 - ((1.0 - color) * (1.0 - color) * 2.0); + } + return color; +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float4 params = float4(0.748, 0.874, 0.241, 0.241); + const float3 W = float3(0.299, 0.587, 0.114); + const float3x3 saturateMatrix = float3x3( + float3(1.1102, -0.0598, -0.061), + float3(-0.0774, 1.0826, -0.1186), + float3(-0.0228, -0.0228, 1.1772)); + + float3 centralColor = tex.sample(s, data.uv).rgb; + + float2 blurCoordinates[24]; + blurCoordinates[0] = data.uv + uResolution * float2(0.0, -10.0); + blurCoordinates[1] = data.uv + uResolution * float2(0.0, 10.0); + blurCoordinates[2] = data.uv + uResolution * float2(-10.0, 0.0); + blurCoordinates[3] = data.uv + uResolution * float2(10.0, 0.0); + blurCoordinates[4] = data.uv + uResolution * float2(5.0, -8.0); + blurCoordinates[5] = data.uv + uResolution * float2(5.0, 8.0); + blurCoordinates[6] = data.uv + uResolution * float2(-5.0, 8.0); + blurCoordinates[7] = data.uv + uResolution * float2(-5.0, -8.0); + blurCoordinates[8] = data.uv + uResolution * float2(8.0, -5.0); + blurCoordinates[9] = data.uv + uResolution * float2(8.0, 5.0); + blurCoordinates[10] = data.uv + uResolution * float2(-8.0, 5.0); + blurCoordinates[11] = data.uv + uResolution * float2(-8.0, -5.0); + blurCoordinates[12] = data.uv + uResolution * float2(0.0, -6.0); + blurCoordinates[13] = data.uv + uResolution * float2(0.0, 6.0); + blurCoordinates[14] = data.uv + uResolution * float2(6.0, 0.0); + blurCoordinates[15] = data.uv + uResolution * float2(-6.0, 0.0); + blurCoordinates[16] = data.uv + uResolution * float2(-4.0, -4.0); + blurCoordinates[17] = data.uv + uResolution * float2(-4.0, 4.0); + blurCoordinates[18] = data.uv + uResolution * float2(4.0, -4.0); + blurCoordinates[19] = data.uv + uResolution * float2(4.0, 4.0); + blurCoordinates[20] = data.uv + uResolution * float2(-2.0, -2.0); + blurCoordinates[21] = data.uv + uResolution * float2(-2.0, 2.0); + blurCoordinates[22] = data.uv + uResolution * float2(2.0, -2.0); + blurCoordinates[23] = data.uv + uResolution * float2(2.0, 2.0); + + float sampleColor = centralColor.g * 22.0; + for (int i = 0; i < 12; i++) { + sampleColor += tex.sample(s, blurCoordinates[i]).g; + } + for (int i = 12; i < 20; i++) { + sampleColor += tex.sample(s, blurCoordinates[i]).g * 2.0; + } + for (int i = 20; i < 24; i++) { + sampleColor += tex.sample(s, blurCoordinates[i]).g * 3.0; + } + sampleColor = sampleColor / 62.0; + + float highPass = centralColor.g - sampleColor + 0.5; + for (int i = 0; i < 5; i++) { + highPass = beautyHardLight(highPass); + } + float luminance = dot(centralColor, W); + float alpha = pow(luminance, params.r); + + float3 smoothColor = centralColor + (centralColor - float3(highPass)) * alpha * 0.1; + smoothColor.r = clamp(pow(smoothColor.r, params.g), 0.0, 1.0); + smoothColor.g = clamp(pow(smoothColor.g, params.g), 0.0, 1.0); + smoothColor.b = clamp(pow(smoothColor.b, params.g), 0.0, 1.0); + + float3 screen = float3(1.0) - (float3(1.0) - smoothColor) * (float3(1.0) - centralColor); + float3 lighten = max(smoothColor, centralColor); + float3 softLight = 2.0 * centralColor * smoothColor + centralColor * centralColor + - 2.0 * centralColor * centralColor * smoothColor; + + float4 color = float4(mix(centralColor, screen, alpha), 1.0); + color.rgb = mix(color.rgb, lighten, alpha); + color.rgb = mix(color.rgb, softLight, params.b); + + float3 satColor = color.rgb * saturateMatrix; + color.rgb = mix(color.rgb, satColor, params.a); + + color.rgb = color.rgb + float3(-0.096); + return color; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlackFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlackFilter.metal new file mode 100644 index 0000000..c58fc46 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlackFilter.metal @@ -0,0 +1,14 @@ +// +// BlackFilter.metal +// RootEncoder +// +// Ported from Android black_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4(0.0, 0.0, 0.0, pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlurFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlurFilter.metal new file mode 100644 index 0000000..27b9565 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BlurFilter.metal @@ -0,0 +1,25 @@ +// +// BlurFilter.metal +// RootEncoder +// +// Ported from Android blur_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uBlur [[buffer(0)]], + constant float &uRadius [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float3 sum = float3(0.0); + if (uBlur > 0.0) { + for (float i = -uBlur; i < uBlur; i++) { + for (float j = -uBlur; j < uBlur; j++) { + sum += tex.sample(s, data.uv + float2(i, j) * (uRadius / uBlur)).rgb / pow(uBlur * 2.0, 2.0); + } + } + } else { + sum = tex.sample(s, data.uv).rgb; + } + return float4(sum, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BrightnessFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BrightnessFilter.metal new file mode 100644 index 0000000..6623cc5 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/BrightnessFilter.metal @@ -0,0 +1,15 @@ +// +// BrightnessFilter.metal +// RootEncoder +// +// Ported from Android brightness_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uBrightness [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4(pixel.rgb + float3(uBrightness), pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CartoonFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CartoonFilter.metal new file mode 100644 index 0000000..7ca82aa --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CartoonFilter.metal @@ -0,0 +1,37 @@ +// +// CartoonFilter.metal +// RootEncoder +// +// Ported from Android cartoon_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uCartoon [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float3 t = tex.sample(s, data.uv).rgb; + float3 t00 = tex.sample(s, data.uv + float2(-uCartoon, -uCartoon)).rgb; + float3 t10 = tex.sample(s, data.uv + float2(uCartoon, -uCartoon)).rgb; + float3 t01 = tex.sample(s, data.uv + float2(-uCartoon, uCartoon)).rgb; + float3 t11 = tex.sample(s, data.uv + float2(uCartoon, uCartoon)).rgb; + float3 tm = (t00 + t01 + t10 + t11) / 4.0; + t = t - tm; + t = t * t * t; + float3 v = 10000.0 * t; + float g = (tm.x - 0.3) * 5.0; + float3 col0 = float3(0.0, 0.0, 0.0); + float3 col1 = float3(0.2, 0.5, 1.0); + float3 col2 = float3(1.0, 0.8, 0.7); + float3 col3 = float3(1.0, 1.0, 1.0); + float3 c; + if (g > 2.0) c = mix(col2, col3, g - 2.0); + else if (g > 1.0) c = mix(col1, col2, g - 1.0); + else c = mix(col0, col1, g); + c = clamp(c, 0.0, 1.0); + v = clamp(v, 0.0, 1.0); + v = c * (1.0 - v); + v = clamp(v, 0.0, 1.0); + if (all(v == col0)) v = col3; + return float4(v, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ChromaticAberrationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ChromaticAberrationFilter.metal new file mode 100644 index 0000000..5afdab1 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ChromaticAberrationFilter.metal @@ -0,0 +1,27 @@ +// +// ChromaticAberrationFilter.metal +// RootEncoder +// +// Ported from Android chromatic_aberration_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float amount = (1.0 + sin(uTime * 6.0)) * 0.5; + amount *= 1.0 + sin(uTime * 16.0) * 0.5; + amount *= 1.0 + sin(uTime * 19.0) * 0.5; + amount *= 1.0 + sin(uTime * 27.0) * 0.5; + amount = pow(amount, 3.0); + amount *= 0.05; + + float3 col; + col.r = tex.sample(s, float2(data.uv.x + amount, data.uv.y)).r; + col.g = tex.sample(s, data.uv).g; + col.b = tex.sample(s, float2(data.uv.x - amount, data.uv.y)).b; + + col *= (1.0 - amount * 0.5); + return float4(col, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CircleFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CircleFilter.metal new file mode 100644 index 0000000..4e1766d --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CircleFilter.metal @@ -0,0 +1,30 @@ +// +// CircleFilter.metal +// RootEncoder +// +// Ported from Android circle_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uRadius [[buffer(0)]], + constant float2 &uCenter [[buffer(1)]], + constant float2 &uResolution [[buffer(2)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 uv; + float2 center; + if (uResolution.x < uResolution.y) { + float scale = uResolution.y / uResolution.x; + uv = float2(data.uv.x, data.uv.y * scale); + center = float2(uCenter.x, uCenter.y * scale); + } else { + float scale = uResolution.x / uResolution.y; + uv = float2(data.uv.x * scale, data.uv.y); + center = float2(uCenter.x * scale, uCenter.y); + } + + float dist = length(uv - center); + if (dist < uRadius) return tex.sample(s, data.uv); + return float4(0.0, 0.0, 0.0, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ColorFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ColorFilter.metal new file mode 100644 index 0000000..b03a435 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ColorFilter.metal @@ -0,0 +1,17 @@ +// +// ColorFilter.metal +// RootEncoder +// +// Ported from Android color_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float3 &uColor [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float3 luma = float3(0.299, 0.587, 0.114); + float4 pixel = tex.sample(s, data.uv); + float grey = dot(pixel.rgb, luma); + return float4(grey * uColor, pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ContrastFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ContrastFilter.metal new file mode 100644 index 0000000..81bd13d --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ContrastFilter.metal @@ -0,0 +1,15 @@ +// +// ContrastFilter.metal +// RootEncoder +// +// Ported from Android contrast_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uContrast [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4((pixel.rgb - float3(0.5)) * uContrast + float3(0.5), pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CropFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CropFilter.metal new file mode 100644 index 0000000..92e7147 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/CropFilter.metal @@ -0,0 +1,18 @@ +// +// CropFilter.metal +// RootEncoder +// +// Ported from Android CropFilterRender (vertex matrix crop implemented as uv remap). +// uArea is (offsetX, offsetY, width, height) normalized 0.0 to 1.0 from top left corner. +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float4 &uArea [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + //uv origin is bottom left, crop area origin is top left + float x = uArea.x + data.uv.x * uArea.z; + float y = 1.0 - (uArea.y + (1.0 - data.uv.y) * uArea.w); + return tex.sample(s, float2(x, y)); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DistortedTvFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DistortedTvFilter.metal new file mode 100644 index 0000000..491dc84 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DistortedTvFilter.metal @@ -0,0 +1,90 @@ +// +// DistortedTvFilter.metal +// RootEncoder +// +// Ported from Android distorted_tv_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float3 distortedMod289(float3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +inline float2 distortedMod289(float2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +inline float3 distortedPermute(float3 x) { + return distortedMod289(((x * 34.0) + 1.0) * x); +} + +inline float distortedSnoise(float2 v) { + const float4 C = float4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + + float2 i = floor(v + dot(v, C.yy)); + float2 x0 = v - i + dot(i, C.xx); + + float2 i1; + i1 = (x0.x > x0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0); + float4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = distortedMod289(i); + float3 p = distortedPermute(distortedPermute(i.y + float3(0.0, i1.y, 1.0)) + + i.x + float3(0.0, i1.x, 1.0)); + + float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); + m = m * m; + m = m * m; + + float3 x = 2.0 * fract(p * C.www) - 1.0; + float3 h = abs(x) - 0.5; + float3 ox = floor(x + 0.5); + float3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + float3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +inline float distortedStaticV(float2 uv, float uTime) { + float staticHeight = distortedSnoise(float2(9.0, uTime * 1.2 + 3.0)) * 0.3 + 5.0; + float staticAmount = distortedSnoise(float2(1.0, uTime * 1.2 - 6.0)) * 0.1 + 0.3; + float staticStrength = distortedSnoise(float2(-9.75, uTime * 0.6 - 3.0)) * 2.0 + 2.0; + return (1.0 - step(distortedSnoise(float2(5.0 * pow(uTime, 2.0) + pow(uv.x * 7.0, 1.2), pow((glslMod(uTime, 100.0) + 100.0) * uv.y * 0.3 + 3.0, staticHeight))), staticAmount)) * staticStrength; +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + + float jerkOffset = (1.0 - step(distortedSnoise(float2(uTime * 1.3, 5.0)), 0.8)) * 0.05; + float fuzzOffset = distortedSnoise(float2(uTime * 15.0, data.uv.y * 80.0)) * 0.003; + float largeFuzzOffset = distortedSnoise(float2(uTime * 1.0, data.uv.y * 25.0)) * 0.004; + float vertJerk = (1.0 - step(distortedSnoise(float2(uTime * 1.5, 5.0)), 0.6)); + float vertJerk2 = (1.0 - step(distortedSnoise(float2(uTime * 5.5, 5.0)), 0.2)); + float yOffset = vertJerk * vertJerk2 * 0.3; + float y = glslMod(data.uv.y + yOffset, 1.0); + float xOffset = fuzzOffset + largeFuzzOffset; + + float staticVal = 0.0; + for (float i = -1.0; i <= 1.0; i += 1.0) { + float maxDist = 5.0 / 200.0; + float dist = i / 200.0; + staticVal += distortedStaticV(float2(data.uv.x, data.uv.y + dist), uTime) * (maxDist - abs(dist)) * 1.5; + } + + float red = tex.sample(s, float2(data.uv.x + xOffset - 0.01, y)).r + staticVal; + float green = tex.sample(s, float2(data.uv.x + xOffset, y)).g + staticVal; + float blue = tex.sample(s, float2(data.uv.x + xOffset + 0.01, y)).b + staticVal; + + float3 color = float3(red, green, blue); + float scanline = sin(data.uv.y * 800.0) * 0.04; + color -= scanline; + + return float4(color, 1.0) + jerkOffset * 0.0; //keep jerkOffset referenced like original +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DuotoneFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DuotoneFilter.metal new file mode 100644 index 0000000..96bac04 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/DuotoneFilter.metal @@ -0,0 +1,18 @@ +// +// DuotoneFilter.metal +// RootEncoder +// +// Ported from Android duotone_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float3 &uColor [[buffer(0)]], + constant float3 &uColor2 [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float3 luma = float3(0.299, 0.587, 0.114); + float4 pixel = tex.sample(s, data.uv); + float grey = dot(pixel.rgb, luma); + return float4((1.0 - grey) * uColor + grey * uColor2, pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EarlyBirdFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EarlyBirdFilter.metal new file mode 100644 index 0000000..c95b2d0 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EarlyBirdFilter.metal @@ -0,0 +1,54 @@ +// +// EarlyBirdFilter.metal +// RootEncoder +// +// Ported from Android earlybird_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float3x3 earlyBirdSaturationMatrix(float saturation) { + float3 luminance = float3(0.3086, 0.6094, 0.0820); + float oneMinusSat = 1.0 - saturation; + float3 red = float3(luminance.x * oneMinusSat); + red.r += saturation; + + float3 green = float3(luminance.y * oneMinusSat); + green.g += saturation; + + float3 blue = float3(luminance.z * oneMinusSat); + blue.b += saturation; + + return float3x3(red, green, blue); +} + +inline void earlyBirdLevels(thread float3 &col, float3 inleft, float3 inright, float3 outleft, float3 outright) { + col = clamp(col, inleft, inright); + col = (col - inleft) / (inright - inleft); + col = outleft + col * (outright - outleft); +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float3 col = tex.sample(s, data.uv).rgb; + float2 fragCoord = data.uv * uResolution; + float2 coord = (fragCoord + fragCoord - uResolution) / uResolution.y; + float3 gradient = float3(pow(1.0 - length(coord * 0.4), 0.6) * 1.2); + float3 grey = float3(184.0 / 255.0); + float3 tint = float3(252.0, 243.0, 213.0) / 255.0; + col = earlyBirdSaturationMatrix(0.68) * col; + earlyBirdLevels(col, float3(0.0), float3(1.0), float3(27.0, 0.0, 0.0) / 255.0, float3(255.0) / 255.0); + col = pow(col, float3(1.19)); + //brightnessAdjust + col += 0.13; + //contrastAdjust + float t = 0.5 - 1.05 * 0.5; + col = col * 1.05 + t; + + col = earlyBirdSaturationMatrix(0.85) * col; + earlyBirdLevels(col, float3(0.0), float3(235.0 / 255.0), float3(0.0, 0.0, 0.0) / 255.0, float3(255.0) / 255.0); + col = mix(tint * col, col, gradient); + col = 1.0 - (1.0 - col) / grey; //colorBurn + return float4(col, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EdgeDetectionFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EdgeDetectionFilter.metal new file mode 100644 index 0000000..32e44b9 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/EdgeDetectionFilter.metal @@ -0,0 +1,15 @@ +// +// EdgeDetectionFilter.metal +// RootEncoder +// +// Ported from Android edge_detection_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 color = tex.sample(s, data.uv); + float gray = length(color.rgb); + return float4(float3(step(0.06, length(float2(dfdx(gray), dfdy(gray))))), 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ExposureFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ExposureFilter.metal new file mode 100644 index 0000000..0ddc9e5 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ExposureFilter.metal @@ -0,0 +1,15 @@ +// +// ExposureFilter.metal +// RootEncoder +// +// Ported from Android exposure_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uExposure [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4(pixel.rgb * pow(2.0, uExposure), pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FilterVertex.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FilterVertex.metal index 6acdd80..e8d6c87 100644 --- a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FilterVertex.metal +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FilterVertex.metal @@ -16,6 +16,12 @@ struct FilterData { float2 uv; }; +//GLSL style mod (uses floor). Metal fmod truncates toward zero and differs with negative values +inline float glslMod(float x, float y) { return x - y * floor(x / y); } +inline float2 glslMod(float2 x, float y) { return x - y * floor(x / y); } +inline float3 glslMod(float3 x, float y) { return x - y * floor(x / y); } +inline float4 glslMod(float4 x, float y) { return x - y * floor(x / y); } + vertex FilterData vertexFilter(uint id [[vertex_id]]) { const float2 vertices[4] = { float2(-1, -1), float2(1, -1), float2(-1, 1), float2(1, 1) }; FilterData data; diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FireFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FireFilter.metal new file mode 100644 index 0000000..c62f2f5 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/FireFilter.metal @@ -0,0 +1,25 @@ +// +// FireFilter.metal +// RootEncoder +// +// Ported from Android fire_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float2 texel_size = float2(6.0, 6.0); + float2 fragCoord = data.uv * uResolution; + fragCoord = floor(fragCoord / texel_size); //Pixelify + fragCoord /= uResolution / texel_size; //Correct scale + float reaction_coordinate = tex.sample(s, fragCoord).r; //Use red channel + float mixval = ((reaction_coordinate - 0.55) * 10.0 + 0.5) * 2.0; + float4 color = float4(mix(float3(1.0, 0.58, 0.0), float3(1.0, 0.7, 0.4), mixval), reaction_coordinate); + color.rgb = float3(1.0, 0.2, 0.0); //Red + if (color.a > 0.65) color.rgb = float3(1.0, 1.0, 1.0); //White + else if (color.a > 0.37) color.rgb = float3(1.4, 0.8, 0.0); //Yellow + color.a = float(color.a > 0.1); + return color; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GammaFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GammaFilter.metal new file mode 100644 index 0000000..dad4207 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GammaFilter.metal @@ -0,0 +1,15 @@ +// +// GammaFilter.metal +// RootEncoder +// +// Ported from Android gamma_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uGamma [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4(pow(pixel.rgb, float3(uGamma)), pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GlitchFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GlitchFilter.metal new file mode 100644 index 0000000..2045b94 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GlitchFilter.metal @@ -0,0 +1,78 @@ +// +// GlitchFilter.metal +// RootEncoder +// +// Ported from Android glitch_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float3 glitchMod289(float3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +inline float2 glitchMod289(float2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +inline float3 glitchPermute(float3 x) { + return glitchMod289(((x * 34.0) + 1.0) * x); +} + +inline float glitchSnoise(float2 v) { + const float4 C = float4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + float2 i = floor(v + dot(v, C.yy)); + float2 x0 = v - i + dot(i, C.xx); + + float2 i1; + i1 = (x0.x > x0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0); + float4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = glitchMod289(i); + float3 p = glitchPermute(glitchPermute(i.y + float3(0.0, i1.y, 1.0)) + + i.x + float3(0.0, i1.x, 1.0)); + + float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); + m = m * m; + m = m * m; + + float3 x = 2.0 * fract(p * C.www) - 1.0; + float3 h = abs(x) - 0.5; + float3 ox = floor(x + 0.5); + float3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + float3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +inline float glitchRand(float2 co) { + return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453); +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float time = uTime * 2.0; + // Create large, incidental noise waves + float noise = max(0.0, glitchSnoise(float2(time, data.uv.y * 0.3)) - 0.3) * (1.0 / 0.7); + // Offset by smaller, constant noise waves + noise = noise + (glitchSnoise(float2(time * 10.0, data.uv.y * 2.4)) - 0.5) * 0.15; + // Apply the noise as x displacement for every line + float xpos = data.uv.x - noise * noise * 0.25; + float4 color = tex.sample(s, float2(xpos, data.uv.y)); + // Mix in some random interference for lines + color.rgb = mix(color.rgb, float3(glitchRand(float2(data.uv.y * time))), noise * 0.3); + // Apply a line pattern every 4 pixels + if (floor(glslMod(color.y * 0.25, 2.0)) == 0.0) { + color.rgb *= 1.0 - (0.15 * noise); + } + // Shift green/blue channels (using the red channel) + color.g = mix(color.r, tex.sample(s, float2(xpos + noise * 0.05, data.uv.y)).g, 0.25); + color.b = mix(color.r, tex.sample(s, float2(xpos - noise * 0.05, data.uv.y)).b, 0.25); + return color; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GreyScaleFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GreyScaleFilter.metal new file mode 100644 index 0000000..6215bda --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/GreyScaleFilter.metal @@ -0,0 +1,16 @@ +// +// GreyScaleFilter.metal +// RootEncoder +// +// Ported from Android grey_scale_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float3 luma = float3(0.299, 0.587, 0.114); + float4 pixel = tex.sample(s, data.uv); + float grey = dot(pixel.rgb, luma); + return float4(grey, grey, grey, pixel.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/HalftoneLinesFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/HalftoneLinesFilter.metal new file mode 100644 index 0000000..3548e3f --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/HalftoneLinesFilter.metal @@ -0,0 +1,82 @@ +// +// HalftoneLinesFilter.metal +// RootEncoder +// +// Ported from Android halftone_lines_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float halftoneRgbToGray(float4 rgba) { + const float3 W = float3(0.2125, 0.7154, 0.0721); + return dot(rgba.xyz, W); +} + +inline float halftoneAverageGray(texture2d tex, float2 uv, float stepX, float stepY) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 result = float4(0.0); + result += tex.sample(s, uv + float2(-stepX, stepY)); + result += tex.sample(s, uv + float2(0.0, stepY)); + result += tex.sample(s, uv + float2(stepX, stepY)); + result += tex.sample(s, uv + float2(-stepX, 0.0)); + result += tex.sample(s, uv); + result += tex.sample(s, uv + float2(stepX, 0.0)); + result += tex.sample(s, uv + float2(-stepX, -stepY)); + result += tex.sample(s, uv + float2(0.0, -stepY)); + result += tex.sample(s, uv + float2(stepX, -stepY)); + return halftoneRgbToGray(result) / 9.0; +} + +inline float2 halftoneRotateCoord(float2 uv, float rads) { + uv = uv * float2x2(float2(cos(rads), sin(rads)), float2(-sin(rads), cos(rads))); + return uv; +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]], + constant float &uMode [[buffer(1)]], + constant float &uRows [[buffer(2)]], + constant float &uRotation [[buffer(3)]], + constant float &uAntialias [[buffer(4)]], + constant float2 &uSampleDist [[buffer(5)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + // halftone line coords + float2 uvRow = fract(halftoneRotateCoord(data.uv, uRotation) * uRows); + float2 uvFloorY = float2(data.uv.x, floor(data.uv.y * uRows) / uRows) + float2(0.0, (1.0 / uRows) * 0.5); + // get averaged gray for row + float averagedBW = halftoneAverageGray(tex, uvFloorY, uSampleDist.x / uResolution.x, uSampleDist.y / uResolution.y); + // use averaged gray to set line thickness + float3 finalColor = float3(1.0); + if (uMode == 1.0) { + if (uvRow.y > averagedBW) finalColor = float3(0.0); + } else if (uMode == 2.0) { + if (abs(uvRow.y + 0.5 - averagedBW * 2.0) < 0.2) finalColor = float3(0.0); + } else if (uMode == 3.0) { + float distFromRowCenter = 1.0 - abs(uvRow.y - 0.5) * 2.0; + finalColor = float3(1.0 - smoothstep(averagedBW - uAntialias, averagedBW + uAntialias, distFromRowCenter)); + } else if (uMode == 4.0) { + float2 uvRow2 = fract(halftoneRotateCoord(data.uv, -uRotation) * uRows); + float distFromRowCenter1 = 1.0 - abs(uvRow.y - 0.5) * 2.0; + float distFromRowCenter2 = 1.0 - abs(uvRow2.y - 0.5) * 2.0; + float distFromRowCenter = min(distFromRowCenter1, distFromRowCenter2); + finalColor = float3(1.0 - smoothstep(averagedBW - uAntialias, averagedBW + uAntialias, distFromRowCenter)); + } else if (uMode == 5.0) { + float2 uvRow2 = fract(halftoneRotateCoord(data.uv, -uRotation) * uRows); + float distFromRowCenter1 = 1.0 - abs(uvRow.y - 0.5) * 2.0; + float distFromRowCenter2 = 1.0 - abs(uvRow2.y - 0.5) * 2.0; + float distFromRowCenter = mix(distFromRowCenter1, distFromRowCenter2, 0.5); + finalColor = float3(1.0 - smoothstep(averagedBW - uAntialias, averagedBW + uAntialias, distFromRowCenter)); + } else if (uMode == 6.0) { + float rot = floor(averagedBW * 6.28) / 6.28; + rot = rot * 4.0; + float2 uvRowLocal = fract(halftoneRotateCoord(data.uv, rot) * uRows); + float distFromRowCenter = 1.0 - abs(uvRowLocal.y - 0.5) * 2.0; + finalColor = float3(1.0 - smoothstep(averagedBW - uAntialias, averagedBW + uAntialias, distFromRowCenter)); + } else if (uMode == 7.0) { + float4 originalColor = tex.sample(s, uvFloorY); + float distFromRowCenter = 1.0 - abs(uvRow.y - 0.5) * 2.0; + float mixValue = 1.0 - smoothstep(averagedBW - uAntialias, averagedBW + uAntialias, distFromRowCenter); + finalColor = mix(originalColor.rgb, float3(1.0), mixValue); + } + return float4(finalColor, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/Image70sFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/Image70sFilter.metal new file mode 100644 index 0000000..ab084e6 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/Image70sFilter.metal @@ -0,0 +1,18 @@ +// +// Image70sFilter.metal +// RootEncoder +// +// Ported from Android image70s_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 texColor = tex.sample(s, data.uv); + float avg = (texColor.r + texColor.g + texColor.b) / 3.0; //grayscale + texColor.r *= abs(cos(avg)); + texColor.g *= abs(sin(avg)); + texColor.b *= abs(atan(avg) * sin(avg)); + return texColor; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/LamoishFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/LamoishFilter.metal new file mode 100644 index 0000000..707204c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/LamoishFilter.metal @@ -0,0 +1,27 @@ +// +// LamoishFilter.metal +// RootEncoder +// +// Ported from Android lamoish_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 color = tex.sample(s, data.uv); + float3 ncolor = float3(0.0, 0.0, 0.0); + float value; + if (color.r < 0.5) value = color.r; + else value = 1.0 - color.r; + float red = 4.0 * value * value * value; + if (color.r < 0.5) ncolor.r = red; + else ncolor.r = 1.0 - red; + if (color.g < 0.5) value = color.g; + else value = 1.0 - color.g; + float green = 2.0 * value * value; + if (color.g < 0.5) ncolor.g = green; + else ncolor.g = 1.0 - green; + ncolor.b = color.b * 0.5 + 0.25; + return float4(ncolor.rgb, color.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/MoneyFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/MoneyFilter.metal new file mode 100644 index 0000000..37e4285 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/MoneyFilter.metal @@ -0,0 +1,47 @@ +// +// MoneyFilter.metal +// RootEncoder +// +// Ported from Android money_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const int kNumPatrones = 6; + + float2 texCoord = data.uv * uResolution; + float2 xy = texCoord.xy / uResolution.yy; + float amplitud = 0.03; + float frecuencia = 10.0; + float gris = 1.0; + float divisor = 8.0 / uResolution.y; + float grosorInicial = divisor * 0.2; + // x: seno del angulo, y: coseno del angulo, z: factor de suavizado + float3 datosPatron[kNumPatrones]; + datosPatron[0] = float3(-0.7071, 0.7071, 3.0); // -45 + datosPatron[1] = float3(0.0, 1.0, 0.6); // 0 + datosPatron[2] = float3(0.0, 1.0, 0.5); // 0 + datosPatron[3] = float3(1.0, 0.0, 0.4); // 90 + datosPatron[4] = float3(1.0, 0.0, 0.3); // 90 + datosPatron[5] = float3(0.0, 1.0, 0.2); // 0 + float4 color = tex.sample(s, float2(texCoord.x / uResolution.x, xy.y)); + for (int i = 0; i < kNumPatrones; i++) { + float coseno = datosPatron[i].x; + float seno = datosPatron[i].y; + float2 punto = float2(xy.x * coseno - xy.y * seno, xy.x * seno + xy.y * coseno); + float grosor = grosorInicial * float(i + 1); + float dist = glslMod(punto.y + grosor * 0.5 - sin(punto.x * frecuencia) * amplitud, divisor); + float brillo = 0.3 * color.r + 0.4 * color.g + 0.3 * color.b; + if (dist < grosor && brillo < 0.75 - 0.12 * float(i)) { + // Suavizado + float k = datosPatron[i].z; + float x = (grosor - dist) / grosor; + float fx = abs((x - 0.5) / k) - (0.5 - k) / k; + gris = min(fx, gris); + } + } + return float4(gris, gris, gris, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NegativeFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NegativeFilter.metal new file mode 100644 index 0000000..8ab03c1 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NegativeFilter.metal @@ -0,0 +1,14 @@ +// +// NegativeFilter.metal +// RootEncoder +// +// Ported from Android negative_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float3 negative = 1.0 - tex.sample(s, data.uv).rgb; + return float4(negative, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NoiseFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NoiseFilter.metal new file mode 100644 index 0000000..d9b2d2f --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/NoiseFilter.metal @@ -0,0 +1,22 @@ +// +// NoiseFilter.metal +// RootEncoder +// +// Ported from Android noise_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float noiseHash(float2 p) { + p = fract(p * 0.3183099) * 50.0; + return fract(p.x * p.y * (p.x + p.y)); +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]], + constant float &uStrength [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 seed = data.uv * 100.0 + uTime; + float r = noiseHash(seed) * 0.01 - 0.005; + return tex.sample(s, data.uv) + float4(r) * uStrength; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PixelatedFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PixelatedFilter.metal new file mode 100644 index 0000000..92b275f --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PixelatedFilter.metal @@ -0,0 +1,15 @@ +// +// PixelatedFilter.metal +// RootEncoder +// +// Ported from Android pixelated_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uPixelated [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 coord = float2(uPixelated * floor(data.uv.x / uPixelated), uPixelated * floor(data.uv.y / uPixelated)); + return tex.sample(s, coord); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PolygonizationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PolygonizationFilter.metal new file mode 100644 index 0000000..34470d6 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/PolygonizationFilter.metal @@ -0,0 +1,54 @@ +// +// PolygonizationFilter.metal +// RootEncoder +// +// Ported from Android polygonization_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float2 polygonHash2(float2 p) { + return fract(sin(float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)))) * 43758.5453); +} + +inline float2 polygonVoronoi(float2 x) { + float2 n = floor(x); + float2 f = fract(x); + float2 mg, mr; + float md = 8.0; + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + float2 g = float2(float(i), float(j)); + float2 o = polygonHash2(n + g); + float2 r = g + o - f; + float d = dot(r, r); + if (d < md) { + md = d; + mr = r; + mg = g; + } + } + } + return mr; +} + +inline float3 polygonVoronoiColor(texture2d tex, float steps, float2 p, float2 uv, float2 uResolution) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 c = polygonVoronoi(steps * p); + float2 uv1 = uv; + uv1.x += c.x / steps; + uv1.y += c.y / steps * uResolution.x / uResolution.y; + return tex.sample(s, float2(uv1.x, uv1.y)).xyz; +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]]) { + float2 p = (data.uv * uResolution).xy / uResolution.xx; + float2 uv = (data.uv * uResolution).xy / uResolution.xy; + float3 color = float3(0.0, 0.0, 0.0); + for (float i = 0.0; i < 4.0; i += 1.0) { + float steps = 30.0 * pow(2.0, i); + color += polygonVoronoiColor(tex, steps, p, uv, uResolution); + } + return float4(color * 0.25, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RainbowFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RainbowFilter.metal new file mode 100644 index 0000000..86f42a8 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RainbowFilter.metal @@ -0,0 +1,37 @@ +// +// RainbowFilter.metal +// RootEncoder +// +// Ported from Android rainbow_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +inline float rainbowRgbToGray(float4 rgba) { + const float3 W = float3(0.2125, 0.7154, 0.0721); + return dot(rgba.xyz, W); +} + +inline float3 rainbowHsv2rgb(float3 c) { + float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float posterSteps = 4.0; + const float lumaMult = 0.5; + const float timeMult = 0.15; + + float4 color = tex.sample(s, data.uv); + float luma = rainbowRgbToGray(color) * lumaMult; + float lumaIndex = floor(luma * posterSteps); + float lumaFloor = lumaIndex / posterSteps; + float lumaRemainder = (luma - lumaFloor) * posterSteps; + if (glslMod(lumaIndex, 2.0) == 0.0) lumaRemainder = 1.0 - lumaRemainder; //flip luma remainder for smooth color transitions + float lumaCycle = glslMod(luma + uTime * timeMult, 1.0); + float3 roygbiv = rainbowHsv2rgb(float3(lumaCycle, 1.0, lumaRemainder)); + return float4(roygbiv, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RgbSaturationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RgbSaturationFilter.metal new file mode 100644 index 0000000..c15255d --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RgbSaturationFilter.metal @@ -0,0 +1,15 @@ +// +// RgbSaturationFilter.metal +// RootEncoder +// +// Ported from Android rgb_saturation_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float3 &uRGBSaturation [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + return float4(pixel.r * uRGBSaturation.r, pixel.g * uRGBSaturation.g, pixel.b * uRGBSaturation.b, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RippleFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RippleFilter.metal new file mode 100644 index 0000000..2da191b --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RippleFilter.metal @@ -0,0 +1,21 @@ +// +// RippleFilter.metal +// RootEncoder +// +// Ported from Android ripple_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]], + constant float &uSpeed [[buffer(1)]], + constant float &uTime [[buffer(2)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 cPos = -1.0 + 2.0 * data.uv; + float ratio = uResolution.x / uResolution.y; + cPos.x *= ratio; + float cLength = length(cPos); + float2 uv = data.uv + (cPos / cLength) * cos(cLength * uSpeed - uTime * uSpeed) * 0.03; + return tex.sample(s, uv); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RotationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RotationFilter.metal new file mode 100644 index 0000000..893362c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/RotationFilter.metal @@ -0,0 +1,26 @@ +// +// RotationFilter.metal +// RootEncoder +// +// Ported from Android RotationFilterRender (vertex matrix rotation implemented as uv rotation). +// uRotation in radians, uFlips is (horizontalFlip, verticalFlip) with 0 or 1 values. +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uRotation [[buffer(0)]], + constant float2 &uFlips [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float2 uv = data.uv; + if (uFlips.x == 1.0) uv.x = 1.0 - uv.x; + if (uFlips.y == 1.0) uv.y = 1.0 - uv.y; + float2 centered = uv - 0.5; + float c = cos(uRotation); + float sn = sin(uRotation); + float2 rotated = float2(centered.x * c - centered.y * sn, centered.x * sn + centered.y * c) + 0.5; + if (rotated.x < 0.0 || rotated.x > 1.0 || rotated.y < 0.0 || rotated.y > 1.0) { + return float4(0.0, 0.0, 0.0, 1.0); + } + return tex.sample(s, rotated); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SaturationFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SaturationFilter.metal new file mode 100644 index 0000000..79481e1 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SaturationFilter.metal @@ -0,0 +1,27 @@ +// +// SaturationFilter.metal +// RootEncoder +// +// Ported from Android saturation_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uShift [[buffer(0)]], + constant float3 &uWeights [[buffer(1)]], + constant float3 &uExponents [[buffer(2)]], + constant float &uSaturation [[buffer(3)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 oldcolor = tex.sample(s, data.uv); + float kv = dot(oldcolor.rgb, uWeights) + uShift; + float3 new_color = uSaturation * oldcolor.rgb + (1.0 - uSaturation) * kv; + float4 result = float4(new_color, oldcolor.a); + + float4 color = tex.sample(s, data.uv); + float de = dot(color.rgb, uWeights); + float inv_de = 1.0 / de; + float3 verynew_color = de * pow(color.rgb * inv_de, uExponents); + float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0); + return result + float4(verynew_color / max_color, color.a); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SepiaFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SepiaFilter.metal new file mode 100644 index 0000000..b5240d6 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SepiaFilter.metal @@ -0,0 +1,18 @@ +// +// SepiaFilter.metal +// RootEncoder +// +// Ported from Android sepia_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + float4 sepia = float4(clamp(pixel.x * 0.393 + pixel.y * 0.769 + pixel.z * 0.189, 0.0, 1.0), + clamp(pixel.x * 0.349 + pixel.y * 0.686 + pixel.z * 0.168, 0.0, 1.0), + clamp(pixel.x * 0.272 + pixel.y * 0.534 + pixel.z * 0.131, 0.0, 1.0), + pixel.w); + return sepia; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SharpnessFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SharpnessFilter.metal new file mode 100644 index 0000000..93b5d16 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SharpnessFilter.metal @@ -0,0 +1,21 @@ +// +// SharpnessFilter.metal +// RootEncoder +// +// Ported from Android sharpness_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float2 &uResolution [[buffer(0)]], + constant float &uSharpness [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 up = tex.sample(s, data.uv + float2(0, 1) / uResolution); + float4 left = tex.sample(s, data.uv + float2(-1, 0) / uResolution); + float4 center = tex.sample(s, data.uv); + float4 right = tex.sample(s, data.uv + float2(1, 0) / uResolution); + float4 down = tex.sample(s, data.uv + float2(0, -1) / uResolution); + + return (1.0 + 4.0 * uSharpness) * center - uSharpness * (up + left + right + down); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SnowFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SnowFilter.metal new file mode 100644 index 0000000..e81805f --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SnowFilter.metal @@ -0,0 +1,39 @@ +// +// SnowFilter.metal +// RootEncoder +// +// Ported from Android snow_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]], + constant float &uLayers [[buffer(1)]], + constant float &uDepth [[buffer(2)]], + constant float &uWidth [[buffer(3)]], + constant float &uSpeed [[buffer(4)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float3x3 p = float3x3( + float3(13.323122, 23.5112, 21.71123), + float3(21.1212, 28.7312, 11.9312), + float3(21.8112, 14.7212, 61.3934)); + + float3 acc = float3(0.0); + float dof = 5.0 * sin(uTime * 0.1); + + for (float i = 0.0; i < uLayers; i++) { + float2 q = data.uv * (1.0 + i * uDepth); + q += float2(q.y * (uWidth * glslMod(i * 7.238917, 1.0) - uWidth * 0.5), uSpeed * uTime / (1.0 + i * uDepth * 0.03)); + float3 n = float3(floor(q), 31.189 + i); + float3 m = floor(n) * 0.00001 + fract(n); + float3 mp = (31415.9 + m) / fract(p * m); + float3 r = fract(mp); + float2 sVal = abs(glslMod(q, 1.0) - 0.5 + 0.9 * r.xy - 0.45); + sVal += 0.01 * abs(2.0 * fract(10.0 * q.yx) - 1.0); + float d = 0.6 * max(sVal.x - sVal.y, sVal.x + sVal.y) + max(sVal.x, sVal.y) - 0.01; + float edge = 0.005 + 0.05 * min(0.5 * abs(i - 5.0 - dof), 1.0); + acc += float3(smoothstep(edge, -edge, d) * (r.x / (1.0 + 0.02 * i * uDepth))); + } + return tex.sample(s, data.uv) + float4(acc, 1.0); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SwirlFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SwirlFilter.metal new file mode 100644 index 0000000..8f7e81c --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/SwirlFilter.metal @@ -0,0 +1,24 @@ +// +// SwirlFilter.metal +// RootEncoder +// +// Ported from Android swirl_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]], + constant float2 &uResolution [[buffer(1)]], + constant float &uRadius [[buffer(2)]], + constant float2 &uCenter [[buffer(3)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + const float PI = 3.14159; + float effectAngle = 2.0 * PI * uTime; + float2 uv = data.uv - uCenter; + float len = length(uv * float2(uResolution.x / uResolution.y, 1.0)); + float angle = atan2(uv.y, uv.x) + effectAngle * smoothstep(uRadius, 0.0, len); + float radius = length(uv); + + return tex.sample(s, float2(radius * cos(angle), radius * sin(angle)) + uCenter); +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/TemperatureFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/TemperatureFilter.metal new file mode 100644 index 0000000..6cf0cef --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/TemperatureFilter.metal @@ -0,0 +1,20 @@ +// +// TemperatureFilter.metal +// RootEncoder +// +// Ported from Android temperature_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTemperature [[buffer(0)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float4 pixel = tex.sample(s, data.uv); + pixel.r = pixel.r + pixel.r * (1.0 - pixel.r) * uTemperature; + pixel.b = pixel.b - pixel.b * (1.0 - pixel.b) * uTemperature; + if (uTemperature > 0.0) pixel.g = pixel.g + pixel.g * (1.0 - pixel.g) * uTemperature * 0.25; + float value = max(pixel.r, max(pixel.g, pixel.b)); + if (value > 1.0) pixel.rgb /= value; + return pixel; +} diff --git a/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ZebraFilter.metal b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ZebraFilter.metal new file mode 100644 index 0000000..af50bf4 --- /dev/null +++ b/RootEncoder/Sources/RootEncoder/encoder/input/metal/shaders/ZebraFilter.metal @@ -0,0 +1,21 @@ +// +// ZebraFilter.metal +// RootEncoder +// +// Ported from Android zebra_fragment.glsl +// Concatenated after FilterVertex.metal at runtime. +// + +fragment float4 fragmentFilter(FilterData data [[stage_in]], + texture2d tex [[texture(0)]], + constant float &uTime [[buffer(0)]], + constant float &uLevels [[buffer(1)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float phase = uTime * 0.5; + float4 color = tex.sample(s, data.uv); + float4 tempColor = color; + tempColor = glslMod(tempColor + phase, 1.0); + tempColor = floor(tempColor * uLevels); + tempColor = glslMod(tempColor, 2.0); + return float4(tempColor.rgb, color.a); +} diff --git a/app/StreamSwiftUIView.swift b/app/StreamSwiftUIView.swift index 98cc37e..432c9eb 100644 --- a/app/StreamSwiftUIView.swift +++ b/app/StreamSwiftUIView.swift @@ -97,6 +97,62 @@ struct StreamSwiftUIView: View, ConnectChecker { } } + private let filterList: [(String, () -> BaseFilterRender)] = [ + ("AnalogTV", { AnalogTVFilterRender() }), + ("Basic deformation", { BasicDeformationFilterRender() }), + ("Beauty", { BeautyFilterRender() }), + ("Black", { BlackFilterRender() }), + ("Blur", { BlurFilterRender() }), + ("Brightness", { BrightnessFilterRender() }), + ("Cartoon", { CartoonFilterRender() }), + ("Chromatic aberration", { ChromaticAberrationFilterRender() }), + ("Circle", { CircleFilterRender() }), + ("Color", { ColorFilterRender() }), + ("Contrast", { ContrastFilterRender() }), + ("Crop", { + let filter = CropFilterRender() + filter.setCropArea(offsetX: 25, offsetY: 25, width: 50, height: 50) + return filter + }), + ("Distorted TV", { DistortedTvFilterRender() }), + ("Duotone", { DuotoneFilterRender() }), + ("Early bird", { EarlyBirdFilterRender() }), + ("Edge detection", { EdgeDetectionFilterRender() }), + ("Exposure", { ExposureFilterRender() }), + ("Fire", { FireFilterRender() }), + ("Gamma", { GammaFilterRender() }), + ("Glitch", { GlitchFilterRender() }), + ("GreyScale", { GreyScaleFilterRender() }), + ("Halftone lines", { HalftoneLinesFilterRender() }), + ("Image 70s", { Image70sFilterRender() }), + ("Lamoish", { LamoishFilterRender() }), + ("Money", { MoneyFilterRender() }), + ("Negative", { NegativeFilterRender() }), + ("Noise", { NoiseFilterRender() }), + ("Pixelated", { PixelatedFilterRender() }), + ("Polygonization", { PolygonizationFilterRender() }), + ("Rainbow", { RainbowFilterRender() }), + ("RGB saturation", { + let filter = RGBSaturationFilterRender() + filter.setRGBSaturation(r: 1.0, g: 0.4, b: 0.4) + return filter + }), + ("Ripple", { RippleFilterRender() }), + ("Rotation", { + let filter = RotationFilterRender() + filter.rotation = 90 + return filter + }), + ("Saturation", { SaturationFilterRender() }), + ("Sepia", { SepiaFilterRender() }), + ("Sharpness", { SharpnessFilterRender() }), + ("Snow", { SnowFilterRender() }), + ("Swirl", { SwirlFilterRender() }), + ("Temperature", { TemperatureFilterRender() }), + ("Vignette", { VignetteShaderFilterRender() }), + ("Zebra", { ZebraFilterRender() }) + ] + var body: some View { ZStack { let filter = FilterUIView() @@ -136,21 +192,6 @@ struct StreamSwiftUIView: View, ConnectChecker { }) { Text("No filter") } - Button(action: { - rtmpStream.metalInterface.setFilter(baseFilterRender: GreyScaleFilterRender()) - }) { - Text("GreyScale") - } - Button(action: { - rtmpStream.metalInterface.setFilter(baseFilterRender: SepiaFilterRender()) - }) { - Text("Sepia") - } - Button(action: { - rtmpStream.metalInterface.setFilter(baseFilterRender: VignetteShaderFilterRender()) - }) { - Text("Vignette (shader)") - } Button(action: { let filterView = ViewFilterRender(view: filter.view) rtmpStream.metalInterface.setFilter(baseFilterRender: filterView) @@ -159,6 +200,13 @@ struct StreamSwiftUIView: View, ConnectChecker { }) { Text("View") } + ForEach(filterList.indices, id: \.self) { index in + Button(action: { + rtmpStream.metalInterface.setFilter(baseFilterRender: filterList[index].1()) + }) { + Text(filterList[index].0) + } + } } }.padding(.trailing, 16) TextField("protocol://ip:port/app/streamname", text: $endpoint)