Easily create pipelines for Metal Shader.
You can easily send any variables to the shader, simply by defining it in your class.
"gid" is pre-defined for [[thread_position_in_grid]]
import EasyMetalShader
@EMComputeShader
class MyCompute {
var intensity: Float = 3
var tex: MTLTexture?
var impl: String {
"float2 floatGid = float2(gid.x, gid.y);"
"float2 center = float2(tex.get_width() / 2, tex.get_height() / 2);"
"float dist = distance(center, floatGid);"
"float color = intensity / dist;"
"tex.write(float4(color, color, color, 1), gid);"
}
var customMetalCode: String {
""
}
}
Same as the compute function, you can send variables to both vertex and fragment shader.
"vertexInput" is pre-defined in vertex function as VertexInput, which stands for [[ stage_in ]].
"c0" is pre-defined in fragment function as float4, which stands for [[ color(0) ]].
"rd" is pre-defined for both vertex and fragment function, which stands for RasterizerData.
//VertexInput
struct VertexInput {
float4 input0 [[ attribute(0) ]]; // this is usually for position
float4 input1 [[ attribute(1) ]]; // this is usually for color
float4 input2 [[ attribute(2) ]]; // use other inputs to pass values to vertex shader
float4 input3 [[ attribute(3) ]];
float4 input4 [[ attribute(4) ]];
float4 input5 [[ attribute(5) ]];
float4 input6 [[ attribute(6) ]];
float4 input7 [[ attribute(7) ]];
float4 input8 [[ attribute(8) ]];
float4 input9 [[ attribute(9) ]];
};
//RasterizerData
struct RasterizerData {
float4 position [[ position ]];
float4 color;
// use this for defining size of MTLPrimitiveType.point
// this has no effect when you are using MTLPrimitiveType other than .point
float size [[ point_size ]];
// use this for passing some variables from vertex shader to fragment shader
float4 temp1;
float4 temp2;
float4 temp3;
float4 temp4;
float4 temp5;
float4 temp6;
float4 temp7;
float4 temp8;
float4 temp9;
// these variables are flat (no interpolation)
float4 flat1 [[ flat ]];
float4 flat2 [[ flat ]];
float4 flat3 [[ flat ]];
float4 flat4 [[ flat ]];
float4 flat5 [[ flat ]];
float4 flat6 [[ flat ]];
float4 flat7 [[ flat ]];
float4 flat8 [[ flat ]];
float4 flat9 [[ flat ]];
};
@EMRenderShader
class MyRender {
var vertImpl: String {
"rd.size = 10;"
"rd.position = vertexInput.input0;"
"rd.color = vertexInput.input1;"
}
var fragImpl: String {
"return rd.color + c0;"
}
var customMetalCode: String {
""
}
}
date and mousePos is defined in ShaderRenderer
import EasyMetalShader
class MyRenderer: ShaderRenderer {
var particles: [VertexInput] = {
var inputs: [VertexInput] = []
for _ in 0...1000 {
var input = VertexInput()
input.input0 = .init(Float.random(in: -1...1), Float.random(in: -1...1), 0, 1)
input.input1 = .init(Float.random(in: 0.3...1), 0.3, 0.3, 1)
inputs.append(input)
}
return inputs
}()
let compute = MyCompute()
let render = MyRender(targetPixelFormat: .bgra8Unorm)
override func draw(view: MTKView, drawable: CAMetalDrawable) {
let dispatch = EMMetalDispatch()
dispatch.compute { [self] encoder in
compute.intensity = abs(sin(Float(Date().timeIntervalSince(date)))) * 100
compute.tex = drawable.texture
compute.dispatch(encoder, textureSizeReference: drawable.texture)
}
dispatch.render(renderTargetTexture: drawable.texture, needsClear: false) { [self] encoder in
render.dispatch(encoder, textureSizeReference: drawable.texture, primitiveType: .point, vertices: particles)
}
dispatch.present(drawable: drawable)
dispatch.commit()
}
}
import SwiftUI
import EasyMetalShader
struct ContentView: View {
let renderer = MyRenderer()
var body: some View {
EasyShaderView(renderer: renderer)
}
}
You can manually dispatch compute or render functions outside of MTKView.
let tex = EMMetalTexture.create(width: 100, height: 100, pixelFormat: .bgra8Unorm, label: "tex")
let dispatch = EMMetalDispatch()
dispatch.compute { encoder in
compute.tex = tex
compute.intensity = 50
compute.dispatch(encoder, textureSizeReference: tex)
}
dispatch.render(renderTargetTexture: tex, needsClear: false) { encoder in
render.dispatch(encoder, textureSizeReference: tex, primitiveType: .point, vertices: [simd_float4(0.2, 0.2, 0, 1.0)])
}
dispatch.commit()
@EMComputeShader
class MyCompute {
@EMIgnore
var someVariable = 3
}
@EMComputeShader
class MyCompute {
init(a: Float) {
setup() // you need this!
}
}
@EMRenderShader
class MyRender {
init(a: Float) {
setup(targetPixelFormat: .bgra8unorm) // you need this!
}
}
- Int32
- Float
- Bool
- simd_int2
- simd_int3
- simd_int4
- simd_float2
- simd_float3
- simd_float4
- simd_float2x2
- simd_float3x3
- simd_float4x4
- MTLTexture (only 2d texture)
- macOS 11.0~
- iOS 14.0~
- iOS Simulator (render shader only works on physical devices)
let dispatch = EMMetalDispatch()
dispatch.custom { commandBuffer in
// do something with commandBuffer
}
dispatch.commit()
implement customMetalCode property to add your original metal codes.
var customMetalCode: String {
"inline float myFunc() {"
"return 1.0;"
"}"
}
// compute shader
@EMComputeShader
class MyCompute {
var intensity: Float = 3
var tex: MTLTexture?
var impl: String {
"float2 floatGid = float2(gid.x, gid.y);"
"float2 center = float2(tex.get_width() / 2, tex.get_height() / 2);"
"float dist = distance(center, floatGid);"
"float color = intensity / dist;"
"tex.write(float4(color, color, color, 1), gid);"
}
var customMetalCode: String {
""
}
}
// vertex/fragment shader
@EMRenderShader
class MyRender {
var vertImpl: String {
"rd.size = 10;"
"rd.position = vertexInput.input0;"
"rd.color = vertexInput.input1;"
}
var fragImpl: String {
"return rd.color + c0;"
}
var customMetalCode: String {
""
}
}
// renderer
class MyRenderer: ShaderRenderer {
var particles: [VertexInput] = {
var inputs: [VertexInput] = []
for _ in 0...1000 {
var input = VertexInput()
input.input0 = .init(Float.random(in: -1...1), Float.random(in: -1...1), 0, 1)
input.input1 = .init(Float.random(in: 0.3...1), 0.3, 0.3, 1)
inputs.append(input)
}
return inputs
}()
let compute = MyCompute()
let render = MyRender(targetPixelFormat: .bgra8Unorm)
override func draw(view: MTKView, drawable: CAMetalDrawable) {
let dispatch = EMMetalDispatch()
dispatch.compute { [self] encoder in
compute.intensity = abs(sin(Float(Date().timeIntervalSince(date)))) * 100
compute.tex = drawable.texture
compute.dispatch(encoder, textureSizeReference: drawable.texture)
}
dispatch.render(renderTargetTexture: drawable.texture, needsClear: false) { [self] encoder in
render.dispatch(encoder, textureSizeReference: drawable.texture, primitiveType: .point, vertices: particles)
}
dispatch.present(drawable: drawable)
dispatch.commit()
}
}
struct ContentView: View {
let renderer = MyRenderer()
var body: some View {
EasyShaderView(renderer: renderer)
}
}