轻松创建 Metal Shader 的管线。
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()
}
}
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()
实现 customMetalCode 属性以添加您自己的 Metal 代码。
var customMetalCode: String {
"inline float myFunc() {"
"return 1.0;"
"}"
}
@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)
}
}