diff --git a/README.md b/README.md index 388529c..5241289 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,4 @@ Repository to accompany the following blog posts: - [Introducing Metal 2](http://metalkit.org/2017/06/30/introducing-metal-2.html) - [Using ARKit with Metal](http://metalkit.org/2017/07/29/using-arkit-with-metal.html) - [Using ARKit with Metal part 2](http://metalkit.org/2017/08/31/using-arkit-with-metal-part-2.html) +- [Working with Particles in Metal](http://metalkit.org/2017/09/30/working-with-particles-in-metal.html) diff --git a/particles/particle.playground/Contents.swift b/particles/particle.playground/Contents.swift new file mode 100644 index 0000000..694d377 --- /dev/null +++ b/particles/particle.playground/Contents.swift @@ -0,0 +1,9 @@ + +import MetalKit +import PlaygroundSupport + +let frame = NSRect(x: 0, y: 0, width: 400, height: 400) +let delegate = MetalView() +let view = MTKView(frame: frame, device: delegate.device) +view.delegate = delegate +PlaygroundPage.current.liveView = view diff --git a/particles/particle.playground/Resources/Shaders.metal b/particles/particle.playground/Resources/Shaders.metal new file mode 100644 index 0000000..dda48b9 --- /dev/null +++ b/particles/particle.playground/Resources/Shaders.metal @@ -0,0 +1,30 @@ + +#include +using namespace metal; + +struct Particle { + float2 center; + float radius; +}; + +float distanceToParticle(float2 point, Particle p) { + return length(point - p.center) - p.radius; +} + +kernel void compute(texture2d output [[texture(0)]], + constant float &time [[buffer(0)]], + uint2 gid [[thread_position_in_grid]]) { + int width = output.get_width(); + int height = output.get_height(); + float2 uv = float2(gid) / float2(width, height); + float2 center = float2(0.5, time); + float radius = 0.05; + float stop = 1 - radius; + if (time >= stop) { center.y = stop; } + else center.y = time; + Particle p = Particle{center, radius}; + float distance = distanceToParticle(uv, p); + float4 color = float4(1, 0.7, 0, 1); + if (distance > 0) { color = float4(0.2, 0.5, 0.7, 1); } + output.write(float4(color), gid); +} diff --git a/particles/particle.playground/Sources/MetalView.swift b/particles/particle.playground/Sources/MetalView.swift new file mode 100644 index 0000000..dde5ba7 --- /dev/null +++ b/particles/particle.playground/Sources/MetalView.swift @@ -0,0 +1,57 @@ + +import MetalKit + +public class MetalView: NSObject, MTKViewDelegate { + + public var device: MTLDevice! = nil + var queue: MTLCommandQueue! = nil + var cps: MTLComputePipelineState! = nil + var timerBuffer: MTLBuffer! = nil + var timer: Float = 0 + + override public init() { + super.init() + device = MTLCreateSystemDefaultDevice() + queue = device.makeCommandQueue() + registerShaders() + } + + func registerShaders() { + guard let path = Bundle.main.path(forResource: "Shaders", ofType: "metal") else { return } + do { + let input = try String(contentsOfFile: path, encoding: String.Encoding.utf8) + let library = try device.makeLibrary(source: input, options: nil) + guard let kernel = library.makeFunction(name: "compute") else { return } + cps = try device.makeComputePipelineState(function: kernel) + } catch let e { + Swift.print("\(e)") + } + timerBuffer = device.makeBuffer(length: MemoryLayout.size, options: []) + } + + func update() { + timer += 0.01 + let bufferPointer = timerBuffer.contents() + memcpy(bufferPointer, &timer, MemoryLayout.size) + } + + public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} + + public func draw(in view: MTKView) { + if let drawable = view.currentDrawable, + let commandBuffer = queue.makeCommandBuffer(), + let commandEncoder = commandBuffer.makeComputeCommandEncoder() + { + commandEncoder.setComputePipelineState(cps) + commandEncoder.setTexture(drawable.texture, index: 0) + commandEncoder.setBuffer(timerBuffer, offset: 0, index: 0) + update() + let threadGroupCount = MTLSizeMake(8, 8, 1) + let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1) + commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount) + commandEncoder.endEncoding() + commandBuffer.present(drawable) + commandBuffer.commit() + } + } +} diff --git a/particles/particle.playground/contents.xcplayground b/particles/particle.playground/contents.xcplayground new file mode 100644 index 0000000..a93d484 --- /dev/null +++ b/particles/particle.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/particles/particle.playground/playground.xcworkspace/contents.xcworkspacedata b/particles/particle.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/particles/particle.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/particles/particle.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate b/particles/particle.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..4b4b294 Binary files /dev/null and b/particles/particle.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/particles/particle.playground/timeline.xctimeline b/particles/particle.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/particles/particle.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + +