metaballs/MetaballsKit/MarchingSquares.swift

160 lines
5.3 KiB
Swift
Raw Normal View History

2018-10-13 12:40:53 -07:00
//
// MarchingSquares.swift
// Metaballs
//
// Created by Eryn Wells on 10/11/18.
// Copyright © 2018 Eryn Wells. All rights reserved.
//
import Foundation
import Metal
2018-10-14 17:26:12 -07:00
import simd
2018-10-13 12:40:53 -07:00
class MarchingSquares {
private var field: Field
2018-10-14 12:16:43 -07:00
private var sampleGridSize = Size(16)
2018-10-13 12:40:53 -07:00
private var semaphore: DispatchSemaphore
2018-10-14 17:26:12 -07:00
private var samplingPipeline: MTLComputePipelineState?
private var parametersBuffer: MTLBuffer?
2018-10-13 12:40:53 -07:00
/// Samples of the field's current state.
private(set) var samplesBuffer: MTLBuffer?
2018-10-13 12:40:53 -07:00
/// Indexes of geometry to render.
2018-10-14 17:26:12 -07:00
private(set) var indexBuffer: MTLBuffer?
2018-10-13 12:40:53 -07:00
private(set) var gridGeometry: MTLBuffer?
private var xSamples: Int {
return Int(field.size.x / sampleGridSize.x)
}
private var ySamples: Int {
return Int(field.size.y / sampleGridSize.y)
}
private var lastSamplesCount = 0
2018-10-14 12:16:43 -07:00
var samplesCount: Int {
return xSamples * ySamples
}
2018-10-13 12:40:53 -07:00
init(field: Field) {
self.field = field
semaphore = DispatchSemaphore(value: 1)
2018-10-13 12:40:53 -07:00
}
2018-10-14 17:26:12 -07:00
func setupMetal(withDevice device: MTLDevice, library: MTLLibrary) {
guard let samplingFunction = library.makeFunction(name: "samplingKernel") else {
fatalError("Couldn't get samplingKernel function from library")
}
do {
samplingPipeline = try device.makeComputePipelineState(function: samplingFunction)
} catch let e {
fatalError("Error building compute pipeline state for sampling kernel: \(e)")
}
let parametersLength = MemoryLayout<simd.packed_int2>.stride * 3 + MemoryLayout<simd.uint>.stride
parametersBuffer = device.makeBuffer(length: parametersLength, options: .storageModeShared)
populateParametersBuffer()
}
func fieldDidResize() {
guard let device = gridGeometry?.device else {
return
}
2018-10-14 17:26:12 -07:00
populateParametersBuffer()
populateGrid(withDevice: device)
populateSamples(withDevice: device)
lastSamplesCount = samplesCount
}
2018-10-14 17:26:12 -07:00
func populateParametersBuffer() {
guard let buffer = parametersBuffer else {
print("Tried to copy parameters buffer before buffer was allocated!")
return
}
let params: [uint] = [
field.size.x, field.size.y,
uint(xSamples), uint(ySamples),
sampleGridSize.x, sampleGridSize.y,
uint(field.balls.count)
]
memcpy(buffer.contents(), params, MemoryLayout<uint>.stride * params.count)
}
2018-10-14 12:16:43 -07:00
func populateGrid(withDevice device: MTLDevice) {
guard lastSamplesCount != samplesCount else {
return
}
2018-10-14 12:16:43 -07:00
print("Populating grid with (\(xSamples), \(ySamples)) samples")
2018-10-14 12:16:43 -07:00
let gridSizeX = Float(sampleGridSize.x)
let gridSizeY = Float(sampleGridSize.y)
2018-10-14 12:16:43 -07:00
var grid = [Rect]()
grid.reserveCapacity(samplesCount)
2018-10-14 12:16:43 -07:00
for y in 0..<ySamples {
for x in 0..<xSamples {
let transform = Matrix4x4.translation(dx: Float(x) * gridSizeX, dy: Float(y) * gridSizeY, dz: 0.0) * Matrix4x4.scale(x: gridSizeX, y: gridSizeY, z: 1)
let color = Float4(r: 0, g: 1, b: 0, a: 1)
let rect = Rect(transform: transform, color: color)
2018-10-14 12:16:43 -07:00
grid.append(rect)
}
}
2018-10-14 12:16:43 -07:00
if let buffer = device.makeBuffer(length: MemoryLayout<Rect>.stride * samplesCount, options: .storageModeShared) {
memcpy(buffer.contents(), grid, MemoryLayout<Rect>.stride * grid.count)
gridGeometry = buffer
} else {
fatalError("Couldn't create buffer for grid rects")
}
2018-10-13 12:40:53 -07:00
}
func populateSamples(withDevice device: MTLDevice) {
2018-10-14 17:26:12 -07:00
// var samples = [Float]()
// samples.reserveCapacity(samplesCount)
// for ys in 0..<ySamples {
// let y = Float(ys * Int(sampleGridSize.y))
// for xs in 0..<xSamples {
// let x = Float(xs * Int(sampleGridSize.x))
// let sample = field.sample(at: Float2(x: x, y: y))
// samples.append(sample)
// }
// }
let samplesLength = MemoryLayout<Float>.stride * samplesCount
2018-10-14 17:26:12 -07:00
samplesBuffer = device.makeBuffer(length: samplesLength, options: .storageModePrivate)
if samplesBuffer == nil {
fatalError("Couldn't create samplesBuffer!")
}
}
func encodeSamplingKernel(intoBuffer buffer: MTLCommandBuffer) {
guard let samplingPipeline = samplingPipeline else {
print("Encode called before sampling pipeline was set up!")
return
}
guard let encoder = buffer.makeComputeCommandEncoder() else {
print("Couldn't create compute encoder")
return
}
2018-10-14 17:26:12 -07:00
encoder.label = "Sample Field"
encoder.setComputePipelineState(samplingPipeline)
encoder.setBuffer(parametersBuffer, offset: 0, index: 0)
encoder.setBuffer(field.ballBuffer, offset: 0, index: 1)
encoder.setBuffer(samplesBuffer, offset: 0, index: 2)
// Dispatch!
let gridSize = MTLSize(width: xSamples, height: ySamples, depth: 1)
let threadgroupSize = MTLSize(width: xSamples, height: 1, depth: 1)
encoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadgroupSize)
encoder.endEncoding()
}
2018-10-13 12:40:53 -07:00
}