diff --git a/Metaballs/Renderer.swift b/Metaballs/Renderer.swift index a7d8ee0..2fe9287 100644 --- a/Metaballs/Renderer.swift +++ b/Metaballs/Renderer.swift @@ -86,11 +86,11 @@ class Renderer: NSObject, MTKViewDelegate { Vertex(position: Point(x: 1, y: 1), textureCoordinate: Point(x: 1, y: 1)) ] - do { - try field.updateBuffers() - } catch let e { - NSLog("Error updating buffers: \(e)") - } +// do { +// try field.updateBuffers() +// } catch let e { +// NSLog("Error updating buffers: \(e)") +// } let buffer = commandQueue.makeCommandBuffer() buffer.label = "Render" diff --git a/Metaballs/Shaders.metal b/Metaballs/Shaders.metal index f59abc4..9f5ed3d 100644 --- a/Metaballs/Shaders.metal +++ b/Metaballs/Shaders.metal @@ -24,12 +24,8 @@ typedef struct { float2 textureCoordinate; } RasterizerData; -typedef struct { - int2 size; - int numberOfBalls; -} Parameters; - -typedef half3 Ball; +typedef int3 Parameters; +typedef float3 Ball; vertex RasterizerData passthroughVertexShader(uint vid [[vertex_id]], @@ -42,20 +38,21 @@ passthroughVertexShader(uint vid [[vertex_id]], return out; } -float sampleAtPoint(float2, constant Ball*, uint); +float sampleAtPoint(float2, constant Ball*, int); fragment float4 sampleToColorShader(RasterizerData in [[stage_in]], constant Parameters& parameters [[buffer(0)]], constant Ball* balls [[buffer(1)]]) { - const float sample = sampleAtPoint(in.textureCoordinate, balls, parameters.numberOfBalls); + const uint numberOfBalls = parameters.z; + const float sample = sampleAtPoint(in.position.xy, balls, numberOfBalls); float4 out; if (sample > 1.0) { - out = float4(0.0, 1.0, 0.0, 0.0); + out = float4(0.0, 1.0, 0.0, 1.0); } else { - out = float4(0.0, 0.0, 0.0, 0.0); + out = float4(0.2, 0.2, 0.2, 1.0); } return out; } @@ -63,11 +60,11 @@ sampleToColorShader(RasterizerData in [[stage_in]], float sampleAtPoint(float2 point, constant Ball* balls, - uint count) + int count) { float sample = 0.0; - for (uint i = 0; i < count; i++) { - constant Ball& ball = balls[i]; + for (int i = 0; i < count; i++) { + Ball ball = balls[i]; float r2 = ball.z * ball.z; // Radius stored in z coordinate. float xDiff = point.x - ball.x; float yDiff = point.y - ball.y; diff --git a/MetaballsKit/Geometry.swift b/MetaballsKit/Geometry.swift index a68d8da..c56ee59 100644 --- a/MetaballsKit/Geometry.swift +++ b/MetaballsKit/Geometry.swift @@ -26,6 +26,12 @@ public struct Point { } } +extension Point: CustomStringConvertible { + public var description: String { + return "(\(x), \(y))" + } +} + public struct Vector { var dx: Float var dy: Float @@ -39,3 +45,9 @@ public struct Vector { self.dy = dy } } + +extension Vector: CustomStringConvertible { + public var description: String { + return "(\(dx), \(dy))" + } +} diff --git a/MetaballsKit/Metaballs.swift b/MetaballsKit/Metaballs.swift index c45f638..f046a0b 100644 --- a/MetaballsKit/Metaballs.swift +++ b/MetaballsKit/Metaballs.swift @@ -46,17 +46,10 @@ public class Field { balls = balls.filter { bounds.contains($0.bounds) } // Update Metal state as needed. -// updateThreadgroupSizes(withFieldSize: size) -// parametersBuffer = nil -// sampleTexture = nil + populateParametersBuffer() if numberOfBallsBeforeFilter != balls.count { ballBuffer = nil - } - do { - try updateBuffers() - } catch let e { - NSLog("Error updating size: \(e)") - return + populateBallBuffer() } } } @@ -105,6 +98,10 @@ public class Field { let ball = Ball(radius: radius, position: position, velocity: Vector()) balls.append(ball) NSLog("Added ball \(ball); fieldSize=\(size)") + + populateParametersBuffer() + ballBuffer = nil + populateBallBuffer() } // MARK: - Metal Configuration @@ -120,23 +117,55 @@ public class Field { // private var threadgroupSize = MTLSize(width: 16, height: 16, depth: 1) /// Create the Metal buffer containing basic parameters of the simulation. - private func makeParametersBufferIfNeeded(withDevice device: MTLDevice) -> MTLBuffer? { + private func populateParametersBuffer() { if parametersBuffer == nil { - parametersBuffer = device.makeBuffer(length: MemoryLayout.stride * 3, options: []) + guard let device = self.device else { return } + let length = 16 // A Parameters struct in shader-land is an int3, which takes 16 bytes. + parametersBuffer = device.makeBuffer(length: length, options: []) + NSLog("Making parameters buffer, length:\(length)") + } + + if let parameters = parametersBuffer { + var ptr = parameters.contents() + var width = UInt32(size.width) + parameters.addDebugMarker("Width", range: NSRange(location: parameters.contents().distance(to: ptr), length: 4)) + ptr = write(value: &width, to: ptr) + var height = UInt32(size.height) + parameters.addDebugMarker("Height", range: NSRange(location: parameters.contents().distance(to: ptr), length: 4)) + ptr = write(value: &height, to: ptr) + var numberOfBalls = UInt32(self.balls.count) + parameters.addDebugMarker("Number Of Balls", range: NSRange(location: parameters.contents().distance(to: ptr), length: 4)) + ptr = write(value: &numberOfBalls, to: ptr) + NSLog("Populated parameters: w:\(width), h:\(height), n:\(numberOfBalls)") } - return parametersBuffer } /// Create a Metal buffer containing the current set of metaballs. /// @param device The Metal device to use to create the buffer. /// @return A new buffer containing metaball data. - private func makeBallBufferIfNeeded(withDevice device: MTLDevice) -> MTLBuffer? { + private func populateBallBuffer() { if ballBuffer == nil && balls.count > 0 { - let sizeOfBall = MemoryLayout.stride * 3 // A Ball in shader-land is a float3. + guard let device = self.device else { return } + let sizeOfBall = 16 // A Ball in shader-land is a float3, which takes 16 bytes. let length = balls.count * sizeOfBall + NSLog("Making ball buffer, length:\(length)") ballBuffer = device.makeBuffer(length: length, options: []) } - return ballBuffer + + if let ballBuffer = ballBuffer { + var ptr = ballBuffer.contents() + var idx = 0 + for var ball in self.balls { + ballBuffer.addDebugMarker("Ball \(idx)", range: NSRange(location: ballBuffer.contents().distance(to: ptr), length: 16)) + ptr = write(value: &ball.position.x, to: ptr) + ptr = write(value: &ball.position.y, to: ptr) + var r = ball.radius + ptr = write(value: &r, to: ptr) + ptr = ptr.advanced(by: 4) // Skip 4 bytes to maintain alignment. + NSLog("Populated ball: x:\(ball.position.x), y:\(ball.position.y), r:\(r)") + idx += 1 + } + } } /// Create a Metal texture to hold sample values created by the sampling compute shader. @@ -162,37 +191,6 @@ public class Field { // threadgroupCount = MTLSize(width: width + threadgroupSize.width - 1, height: height + threadgroupSize.height - 1, depth: 1) // } - /// Copy metaballs data into the parameters buffer. - public func updateBuffers() throws { - guard let device = self.device else { - throw MetaballsError.metalError("Missing Metal device for update") - } - - guard let parameters = makeParametersBufferIfNeeded(withDevice: device), - let balls = makeBallBufferIfNeeded(withDevice: device) - else { - throw MetaballsError.metalError("Couldn't create buffers") - } - - var ptr = parameters.contents() - - var width = Int(size.width) - ptr = write(value: &width, to: ptr) - var height = Int(size.height) - ptr = write(value: &height, to: ptr) - - var numberOfBalls = self.balls.count - ptr = write(value: &numberOfBalls, to: ptr) - - ptr = balls.contents() - for var ball in self.balls { - ptr = write(value: &ball.position.x, to: ptr) - ptr = write(value: &ball.position.y, to: ptr) - var r = ball.radius - ptr = write(value: &r, to: ptr) - } - } - private func write(value: inout T, to ptr: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { let sizeOfType = MemoryLayout.stride ptr.copyBytes(from: &value, count: sizeOfType) @@ -206,8 +204,8 @@ public class Field { NSLog("Setting up Metal") self.device = device // sampleComputeState = try computePipelineStateForSamplingKernel(withDevice: device) - parametersBuffer = makeParametersBufferIfNeeded(withDevice: device) - ballBuffer = makeBallBufferIfNeeded(withDevice: device) + populateParametersBuffer() + populateBallBuffer() } // public func computePipelineStateForSamplingKernel(withDevice device: MTLDevice) throws -> MTLComputePipelineState? {