From 3bb25af63ab642f1adfecfd840689c7049dcfcf4 Mon Sep 17 00:00:00 2001 From: Marius Horga Date: Sun, 1 Oct 2017 15:25:48 -0500 Subject: [PATCH] added particles --- README.md | 1 + particles/particle.playground/Contents.swift | 9 +++ .../Resources/Shaders.metal | 30 +++++++++ .../Sources/MetalView.swift | 57 ++++++++++++++++++ .../particle.playground/contents.xcplayground | 4 ++ .../contents.xcworkspacedata | 7 +++ .../UserInterfaceState.xcuserstate | Bin 0 -> 13567 bytes .../particle.playground/timeline.xctimeline | 6 ++ 8 files changed, 114 insertions(+) create mode 100644 particles/particle.playground/Contents.swift create mode 100644 particles/particle.playground/Resources/Shaders.metal create mode 100644 particles/particle.playground/Sources/MetalView.swift create mode 100644 particles/particle.playground/contents.xcplayground create mode 100644 particles/particle.playground/playground.xcworkspace/contents.xcworkspacedata create mode 100644 particles/particle.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 particles/particle.playground/timeline.xctimeline 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 0000000000000000000000000000000000000000..4b4b2948f2356d1d13a1eeabe5bd3d81246cf0da GIT binary patch literal 13567 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
H=1uX*EPRs#o7#Q5A7XVw|0?ssdl^eP3>FS zx3vefC$(p^A85~OFK9o}{;0#cFkQGVQYX>H>)`5z&Zsl%QgvN)8Mn{}_~w(EB4cI)2J9nqcB zeXRRbcTsm)cSZM&?pxij`bd3@K29&!EA?uyPS>>rd!U>Cfmt&|lJjZ-_CN47r91!&F1S@Q`7V zVX@(1!&<{S!+OI8!$!l4h8>1IhP{S;hBpnz4euLH8O|Ec8O|HNGJI{gX1H$n!SIuj z7%j$hV>e?DV^8BS<9K7G(PgYQx{Y;4pRwLJ%Xp`8zHxzZq45FZW5%_{b;k9^4aSYe zO~%c}myE9)-!$$w9yA^{9y6XWo;02|erLj_Sd-kOG9{YSCY{M>vY70q-lo2$98;dD zz*J}&WV+2X#8hmmH!U^oG+i)t-JHd;1WHe0q^c3EDv?6tgZ*=IRqIb*qC`NZ;><#Wqb z%Qee)mLDuXS&6l?Rc2LM6RaAm-fFU1ti7!htO4so)+ek_TN|ybtZS{$SvOd>TX$La zSYNa5v%Y2BZ+*x5vGtmbvl(n7Z8L21Z3}D*Z4cTO*_POvZ7OM<~Zm0+Hu|SOAsI-LWFN_y6_$P Ibo~0i0O%R+D*ylh literal 0 HcmV?d00001 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 @@ + + + + +