简易Metal着色器

Release Swift Compatibility Platform Compatibility License

轻松创建 Metal Shader 的管线。

问答 (AI 文档)

https://chatgpt.com/g/g-67a4a543c1048191b24ee44bd805f64a-easymetalshader-library-q-a

用法

创建计算函数

您可以通过在类中定义变量,轻松地将它们发送到着色器。

"gid" 预定义用于 [[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 {
        ""
    }
}

创建渲染函数

与计算函数相同,您可以将变量发送到顶点和片段着色器。

"vertexInput" 在顶点函数中预定义为 VertexInput,它代表 [[ stage_in ]]。

"c0" 在片段函数中预定义为 float4,它代表 [[ color(0) ]]。

"rd" 预定义用于顶点和片段函数,它代表光栅化器数据。

//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 和 mousePos 在 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()
    }
}

在 SwiftUI View 中显示

import SwiftUI
import EasyMetalShader

struct ContentView: View {
    let renderer = MyRenderer()
    var body: some View {
        EasyShaderView(renderer: renderer)
    }
}

注意

手动调度

您可以在 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!
    }
}

支持的数据类型

支持的平台

自定义管线

let dispatch = EMMetalDispatch()
dispatch.custom { commandBuffer in
    // do something with commandBuffer
}
dispatch.commit()

自定义 Metal 函数

实现 customMetalCode 属性以添加您自己的 Metal 代码。

var customMetalCode: String {
    "inline float myFunc() {"
    "return 1.0;"
    "}"
}

用于 3D 纹理的计算着色器

@EMComputeShader3D
class MyCompute3D {
    
    @EMTextureArgument(.read, .type3D)
    var tex: MTLTexture?
    
    var impl: String {
        "float4 c = tex.read(gid);"
    }
    
    var customMetalCode: String {
        ""
    }
}

示例代码

// 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)
    }
}

outoutout