diff --git a/particles/particle2.playground/Contents.swift b/particles/particle2.playground/Contents.swift new file mode 100644 index 0000000..abcee84 --- /dev/null +++ b/particles/particle2.playground/Contents.swift @@ -0,0 +1,10 @@ + +import MetalKit +import PlaygroundSupport + +let frame = NSRect(x: 0, y: 0, width: 400, height: 400) +let delegate = MetalViewDelegate() +let view = MTKView(frame: frame, device: delegate.device) +view.clearColor = MTLClearColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1) +view.delegate = delegate +PlaygroundPage.current.liveView = view diff --git a/particles/particle2.playground/Resources/Shaders.metal b/particles/particle2.playground/Resources/Shaders.metal new file mode 100644 index 0000000..b392e96 --- /dev/null +++ b/particles/particle2.playground/Resources/Shaders.metal @@ -0,0 +1,32 @@ + +#include +using namespace metal; + +struct VertexIn { + float4 position [[attribute(0)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float4 color; +}; + +struct Particle { + float4x4 initial_matrix; + float4x4 matrix; + float4 color; +}; + +vertex VertexOut vertex_main(const VertexIn vertex_in [[stage_in]], + constant Particle *particles [[buffer(1)]], + uint instanceid [[instance_id]]) { + VertexOut vertex_out; + Particle particle = particles[instanceid]; + vertex_out.position = particle.matrix * vertex_in.position ; + vertex_out.color = particle.color; + return vertex_out; +} + +fragment float4 fragment_main(VertexOut vertex_in [[stage_in]]) { + return vertex_in.color; +} diff --git a/particles/particle2.playground/Sources/MetalViewDelegate.swift b/particles/particle2.playground/Sources/MetalViewDelegate.swift new file mode 100644 index 0000000..212a1b3 --- /dev/null +++ b/particles/particle2.playground/Sources/MetalViewDelegate.swift @@ -0,0 +1,98 @@ + +import MetalKit + +public class MetalViewDelegate: NSObject, MTKViewDelegate { + + public var device: MTLDevice! + var queue: MTLCommandQueue! + var pipelineState: MTLRenderPipelineState! + var model: MTKMesh! + var particles: [Particle]! + var particlesBuffer: MTLBuffer! + var timer: Float = 0 + + struct Particle { + var initialMatrix = matrix_identity_float4x4 + var matrix = matrix_identity_float4x4 + var color = float4() + } + + override public init() { + super.init() + initializeMetal() + } + + func initializeBuffers() { + particles = [Particle](repeatElement(Particle(), count: 1000)) + particlesBuffer = device.makeBuffer(length: particles.count * MemoryLayout.stride, options: [])! + var pointer = particlesBuffer.contents().bindMemory(to: Particle.self, capacity: particles.count) + for _ in particles.enumerated() { + pointer.pointee.initialMatrix = translate(by: [Float(drand48()) / 10, Float(drand48()) * 10, 0]) + pointer.pointee.color = float4(0.2, 0.6, 0.9, 1) + pointer = pointer.advanced(by: 1) + } + let allocator = MTKMeshBufferAllocator(device: device) + let sphere = MDLMesh(sphereWithExtent: [0.01, 0.01, 0.01], segments: [8, 8], inwardNormals: false, geometryType: .triangles, allocator: allocator) + do { + model = try MTKMesh(mesh: sphere, device: device) + } catch let e { + Swift.print("\(e)") + } + } + + func initializeMetal() { + device = MTLCreateSystemDefaultDevice() + queue = device.makeCommandQueue() + initializeBuffers() + let library: MTLLibrary + do { + let path = Bundle.main.path(forResource: "Shaders", ofType: "metal") + let source = try String(contentsOfFile: path!, encoding: .utf8) + library = try device.makeLibrary(source: source, options: nil) + let descriptor = MTLRenderPipelineDescriptor() + descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + descriptor.vertexFunction = library.makeFunction(name: "vertex_main") + descriptor.fragmentFunction = library.makeFunction(name: "fragment_main") + descriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(model.vertexDescriptor) + pipelineState = try device.makeRenderPipelineState(descriptor: descriptor) + } catch let error as NSError { + fatalError("library error: " + error.description) + } + } + + func translate(by: float3) -> float4x4 { + return float4x4(columns: ( + float4( 1, 0, 0, 0), + float4( 0, 1, 0, 0), + float4( 0, 0, 1, 0), + float4( by.x, by.y, by.z, 1) + )) + } + + func update() { + timer += 0.01 + var pointer = particlesBuffer.contents().bindMemory(to: Particle.self, capacity: particles.count) + for _ in particles { + pointer.pointee.matrix = translate(by: [0, -3 * timer, 0]) * pointer.pointee.initialMatrix + pointer = pointer.advanced(by: 1) + } + } + + public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } + + public func draw(in view: MTKView) { + update() + guard let commandBuffer = queue.makeCommandBuffer(), + let descriptor = view.currentRenderPassDescriptor, + let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor), + let drawable = view.currentDrawable else { fatalError() } + let submesh = model.submeshes[0] + commandEncoder.setRenderPipelineState(pipelineState) + commandEncoder.setVertexBuffer(model.vertexBuffers[0].buffer, offset: 0, index: 0) + commandEncoder.setVertexBuffer(particlesBuffer, offset: 0, index: 1) + commandEncoder.drawIndexedPrimitives(type: .triangle, indexCount: submesh.indexCount, indexType: submesh.indexType, indexBuffer: submesh.indexBuffer.buffer, indexBufferOffset: 0, instanceCount: particles.count) + commandEncoder.endEncoding() + commandBuffer.present(drawable) + commandBuffer.commit() + } +} diff --git a/particles/particle2.playground/contents.xcplayground b/particles/particle2.playground/contents.xcplayground new file mode 100644 index 0000000..a93d484 --- /dev/null +++ b/particles/particle2.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/particles/particle2.playground/playground.xcworkspace/contents.xcworkspacedata b/particles/particle2.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/particles/particle2.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/particles/particle2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate b/particles/particle2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..d02d1fb Binary files /dev/null and b/particles/particle2.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate differ