Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/yukiny0811/easymetalshader
Metal Shader! Easy!
https://github.com/yukiny0811/easymetalshader
compute-shader creative-coding generative-art gpgpu ios macos metal shader swift swiftui
Last synced: 3 months ago
JSON representation
Metal Shader! Easy!
- Host: GitHub
- URL: https://github.com/yukiny0811/easymetalshader
- Owner: yukiny0811
- License: apache-2.0
- Created: 2024-01-03T16:32:43.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-08-25T17:01:58.000Z (5 months ago)
- Last Synced: 2024-10-29T03:35:58.482Z (3 months ago)
- Topics: compute-shader, creative-coding, generative-art, gpgpu, ios, macos, metal, shader, swift, swiftui
- Language: Swift
- Homepage:
- Size: 137 KB
- Stars: 9
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# EasyMetalShader
[![Release](https://img.shields.io/github/v/release/yukiny0811/EasyMetalShader)](https://github.com/yukiny0811/EasyMetalShader/releases/latest)
[![Swift Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fyukiny0811%2FEasyMetalShader%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/yukiny0811/EasyMetalShader)
[![Platform Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fyukiny0811%2FEasyMetalShader%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/yukiny0811/EasyMetalShader)
[![License](https://img.shields.io/github/license/yukiny0811/EasyMetalShader)](https://github.com/yukiny0811/EasyMetalShader/blob/main/LICENSE)Easily create pipelines for Metal Shader.
## Usage
### Create Compute Function
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]]
```.swift
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 {
""
}
}
```### Create Render Function
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.
```.metal
//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 ]];
};
``````.swift
@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 {
""
}
}
```### Create pipeline and renderer
date and mousePos is defined in ShaderRenderer
```.swift
import EasyMetalShaderclass 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()
}
}
```### Show in SwiftUI View
```.swift
import SwiftUI
import EasyMetalShaderstruct ContentView: View {
let renderer = MyRenderer()
var body: some View {
EasyShaderView(renderer: renderer)
}
}
```## Notes
### Manual Dispatch
You can manually dispatch compute or render functions outside of MTKView.
```.swift
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()
```### Ignoring variables from being sent to the shader
```.swift
@EMComputeShader
class MyCompute {
@EMIgnore
var someVariable = 3
}
```### Custom Constructor
```.swift
@EMComputeShader
class MyCompute {
init(a: Float) {
setup() // you need this!
}
}@EMRenderShader
class MyRender {
init(a: Float) {
setup(targetPixelFormat: .bgra8unorm) // you need this!
}
}
```### Supported Data Types
- Int32
- Float
- Double
- Bool
- simd_int2
- simd_int3
- simd_int4
- simd_float2
- simd_float3
- simd_float4
- simd_float2x2
- simd_float3x3
- simd_float4x4
- simd_double2
- simd_double3
- simd_double4
- simd_double2x2
- simd_double3x3
- simd_double4x4
- MTLTexture (2d)
- MTLTexture (3d)
- IntBuffer (array)
- Int2Buffer (array)
- Int3Buffer (array)
- Int4Buffer (array)
- BoolBuffer (array)
- FloatBuffer (array)
- Float2Buffer (array)
- Float3Buffer (array)
- Float4Buffer (array)### Supported Platforms
- macOS 11.0~
- iOS 14.0~
- iOS Simulator (render shader only works on physical devices)### Custom Pipelines
```.swift
let dispatch = EMMetalDispatch()
dispatch.custom { commandBuffer in
// do something with commandBuffer
}
dispatch.commit()
```### Custom Metal Functions
implement customMetalCode property to add your original metal codes.
```.swift
var customMetalCode: String {
"inline float myFunc() {"
"return 1.0;"
"}"
}
```### Compute Shader for 3D Textures
```.swift
@EMComputeShader3D
class MyCompute3D {
@EMTextureArgument(.read, .type3D)
var tex: MTLTexture?
var impl: String {
"float4 c = tex.read(gid);"
}
var customMetalCode: String {
""
}
}
```## Sample Code
```.swift
// 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 {
""
}
}
``````.swift
// 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()
}
}
```
```.swift
struct ContentView: View {
let renderer = MyRenderer()
var body: some View {
EasyShaderView(renderer: renderer)
}
}
```![outoutout](https://github.com/yukiny0811/EasyMetalShader/assets/28947703/27c53c0b-941a-4e63-8f2d-ca913029ed81)