diff --git a/Foil.xcodeproj/xcshareddata/xcschemes/Foil.xcscheme b/Foil.xcodeproj/xcshareddata/xcschemes/Foil.xcscheme
new file mode 100644
index 0000000..57e6adb
--- /dev/null
+++ b/Foil.xcodeproj/xcshareddata/xcschemes/Foil.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Foil.xcodeproj/xcuserdata/rob.xcuserdatad/xcschemes/xcschememanagement.plist b/Foil.xcodeproj/xcuserdata/rob.xcuserdatad/xcschemes/xcschememanagement.plist
index e118444..be42479 100644
--- a/Foil.xcodeproj/xcuserdata/rob.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Foil.xcodeproj/xcuserdata/rob.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -10,5 +10,13 @@
0
+ SuppressBuildableAutocreation
+
+ D4AC68B3245CF43F00917569
+
+ primary
+
+
+
diff --git a/Foil/Base.lproj/Main.storyboard b/Foil/Base.lproj/Main.storyboard
index 0ff74c7..fb54775 100644
--- a/Foil/Base.lproj/Main.storyboard
+++ b/Foil/Base.lproj/Main.storyboard
@@ -679,7 +679,7 @@
-
+
diff --git a/Foil/FoilViewController.swift b/Foil/FoilViewController.swift
index f110d6a..8e8f755 100644
--- a/Foil/FoilViewController.swift
+++ b/Foil/FoilViewController.swift
@@ -14,17 +14,24 @@ class FoilViewController: NSViewController, MTKViewDelegate {
assert(false)
}
+ let IntelGPUInMetalDevicesArray = 0
+ let RadeonGPUInMetalDevicesArray = 1
+
+ static let frameTime: Double = 1.0 / 60.0
+ static let second: Double = frameTime * 60.0
+ static let metersPerSecond: Double = 600.0
+
// Table with various simulation configurations. Apps would typically load simulation parameters
// such as these from a file or UI controls, but to simplify the sample and focus on Metal usage,
// this table is hardcoded
static let FoilSimulationConfigTable = [
// damping softening numBodies clusterScale velocityScale renderScale renderBodies simInterval simDuration
- FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 16384, clusterScale: 1.54, velocityScale: 8, renderScale: 25.0, renderBodies: 16384, simInterval: 0.0160, simDuration: 500.0),
- FoilSimulationConfig(damping: 1.0, softeningSqr: 0.100, numBodies: 16384, clusterScale: 0.32, velocityScale: 276, renderScale: 2.5, renderBodies: 16384, simInterval: 0.0006, simDuration: 5.0),
- FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 16384, clusterScale: 0.68, velocityScale: 20, renderScale: 1700.0, renderBodies: 16384, simInterval: 0.0160, simDuration: 5.0),
- FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 16384, clusterScale: 1.54, velocityScale: 8, renderScale: 25.0, renderBodies: 16384, simInterval: 0.0160, simDuration: 5.0),
- FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 16384, clusterScale: 6.04, velocityScale: 0, renderScale: 300.0, renderBodies: 16384, simInterval: 0.0160, simDuration: 5.0),
- FoilSimulationConfig(damping: 1.0, softeningSqr: 0.145, numBodies: 16384, clusterScale: 0.32, velocityScale: 272, renderScale: 2.5, renderBodies: 16384, simInterval: 0.0006, simDuration: 5.0)
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 4096, clusterScale: 0.32, velocityScale: metersPerSecond / 30, renderScale: 2.5, renderBodies: 4096, simInterval: frameTime / 30, simDuration: 10.0 * second / 30),
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 4096, clusterScale: 6.04, velocityScale: 0, renderScale: 75.0, renderBodies: 4096, simInterval: frameTime, simDuration: 10.0 * second),
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 0.145, numBodies: 4096, clusterScale: 0.32, velocityScale: metersPerSecond / 30, renderScale: 2.5, renderBodies: 4096, simInterval: frameTime / 30, simDuration: 10.0 * second / 30),
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 4096, clusterScale: 1.54, velocityScale: metersPerSecond / 30, renderScale: 75.0, renderBodies: 4096, simInterval: frameTime, simDuration: 10.0 * second),
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 0.100, numBodies: 4096, clusterScale: 0.68, velocityScale: metersPerSecond / 30, renderScale: 1000.0, renderBodies: 4096, simInterval: frameTime, simDuration: 10.0 * second),
+ FoilSimulationConfig(damping: 1.0, softeningSqr: 1.000, numBodies: 4096, clusterScale: 1.54, velocityScale: metersPerSecond / 30, renderScale: 75.0, renderBodies: 4096, simInterval: frameTime, simDuration: 10.0 * second)
]
static let FoilNumSimulationConfigs = FoilSimulationConfigTable.count
@@ -33,24 +40,49 @@ class FoilViewController: NSViewController, MTKViewDelegate {
var renderer: FoilRenderer!
var simulation: FoilSimulation!
+ // The current time (in simulation time units) that the simulation has processed
var simulationTime: CFAbsoluteTime = 0
+
+ // When rendering is paused (such as immediately after a simulation has completed), the time
+ // to unpause and continue simulations.
var continuationTime: CFAbsoluteTime = 0
var computeDevice: MTLDevice!
+ // Index of the current simulation config in the simulation config table
var configNum = 0
+
+ // Currently running simulation config
var config: FoilSimulationConfig!
+ // Command queue used when simulation and renderer are using the same device.
+ // Set to nil when using different devices
var commandQueue: MTLCommandQueue!
+ // When true, stop running any more simulations (such as when the window closes).
var terminateAllSimulations = false
+
+ // When true, restart the current simulation if it was interrupted and data could not
+ // be retrieved
var restartSimulation = false
+ // UI showing current simulation name and percentage complete
@IBOutlet var _simulationName: NSTextField!
@IBOutlet var _simulationPercentage: NSTextField!
+ // Timer used to make the text fields blink when results have been completed
var blinker: Timer!
+ let viewControllerispatchQueue = DispatchQueue(
+ label: "viewController.q", qos: .default, attributes: [/*serial*/],
+ target: DispatchQueue.global(qos: .default)
+ )
+
+ let dataUpdateDispatchQueue = DispatchQueue(
+ label: "dataUpdate.q", qos: .default, attributes: [/*serial*/],
+ target: DispatchQueue.global(qos: .default)
+ )
+
override func viewDidLoad() {
super.viewDidLoad()
selectDevices()
@@ -58,16 +90,10 @@ class FoilViewController: NSViewController, MTKViewDelegate {
_view.delegate = self
}
- override var representedObject: Any? {
- didSet { assert(false) }
- }
-
- static let FoilSecondsToPresentSimulationResults = CFTimeInterval(4.0)
-
override func viewDidAppear() { beginSimulation() }
override func viewDidDisappear() {
- LikeObjcSync.synced(self) {
+ viewControllerispatchQueue.sync {
// Stop simulation if on another thread
self.simulation.halt = true;
@@ -81,39 +107,16 @@ class FoilViewController: NSViewController, MTKViewDelegate {
precondition(!availableDevices.isEmpty, "Metal is not supported on this Mac")
- // Select compute device
- for device in availableDevices {
- if device.isRemovable {
- // Select removable device if available since if there is one, it's probably the most
- // powerful device available
- computeDevice = device;
- break;
- } else if device.isHeadless {
- // Select headless device since if there is one it's probably dedicated to compute
- // tasks
- computeDevice = device;
- }
- }
-
- if computeDevice == nil {
- guard let cd = MTLCreateSystemDefaultDevice() else { fatalError() }
- computeDevice = cd
- }
-
+ computeDevice = availableDevices[RadeonGPUInMetalDevicesArray]
NSLog("Selected compute device: \(computeDevice.name)")
- // Select renderer device (stored as _view.device)
-
- // Query for device driving the display
- let key = NSDeviceDescriptionKey("NSScreenNumber")
- let viewDisplayID = (_view.window?.screen?.deviceDescription[key] as? CGDirectDisplayID) ?? CGDirectDisplayID()
-
- let rendererDevice = CGDirectDisplayCopyCurrentMetalDevice(viewDisplayID);
+ // Select renderer device
+ let rendererDevice = availableDevices[RadeonGPUInMetalDevicesArray]
if rendererDevice !== _view.device {
_view.device = rendererDevice;
- NSLog("New render device: '\(_view.device!.name)'")
+ NSLog("New render device: '\(rendererDevice.name)'")
renderer = FoilRenderer(_view)
renderer.drawableSizeWillChange(size: _view.drawableSize)
@@ -151,12 +154,10 @@ class FoilViewController: NSViewController, MTKViewDelegate {
let updateHandler: (NSData, CFAbsoluteTime) -> () = {
// Update the renderer's position data so that it can show forward progress
-// print("update handler st = \($1)")
self.updateWithNewPositionData(updateData: $0, forSimulationTime: $1)
}
let dataProvider: (NSData, NSData, CFAbsoluteTime) -> () = {
- print("dataProvider st = \($2)")
self.handleFullyProvidedSetOfPositionData(positionData: $0, velocityData: $1, forSimulationTime: $2)
}
@@ -166,12 +167,12 @@ class FoilViewController: NSViewController, MTKViewDelegate {
/// Receive and update of new positions for the simulation time given.
func updateWithNewPositionData(updateData: NSData, forSimulationTime simulationTime: CFAbsoluteTime) {
// Lock with updateData so thus thread does not update data during an update on another thread
- LikeObjcSync.synced(updateData) {
+ dataUpdateDispatchQueue.sync {
// Update the renderer's position data so that it can show forward progress
self.renderer.providePositionData(data: updateData)
}
- LikeObjcSync.synced(self) {
+ viewControllerispatchQueue.sync {
// Lock around _simulation time since it will be accessed on another thread
self.simulationTime = simulationTime;
}
@@ -183,14 +184,12 @@ class FoilViewController: NSViewController, MTKViewDelegate {
positionData: NSData, velocityData: NSData,
forSimulationTime simulationTime: CFAbsoluteTime
) {
- LikeObjcSync.synced(self) {
+ viewControllerispatchQueue.sync {
if self.terminateAllSimulations {
NSLog("Terminating all simulations")
return
}
- print("here self.simulationTime = simulationTime: \(self.simulationTime) = \(simulationTime), \(config.simDuration)")
-
self.simulationTime = simulationTime;
if simulationTime >= config.simDuration {
@@ -231,6 +230,8 @@ class FoilViewController: NSViewController, MTKViewDelegate {
/// Called whenever view changes orientation or layout is changed
func drawableSizeWillChange(size: CGSize) { renderer.drawableSizeWillChange(size: size) }
+ static let FoilSecondsToPresentSimulationResults = CFTimeInterval(4.0)
+
/// Called whenever the view needs to render
func draw(in view: MTKView) {
// Number of bodies to render this frame
@@ -303,18 +304,15 @@ class FoilViewController: NSViewController, MTKViewDelegate {
commandBuffer.popDebugGroup()
- let st = simulationTime
simulationTime += Double(config.simInterval)
- print("draw st before \(st) after \(simulationTime)")
} else {
- print("no draw ish \(simulationTime)")
renderer.drawProvidedPositionDataWithNumBodies(numParticles: numBodies, inView: _view)
}
var percentComplete = 0
// Lock when using _simulationTime since it can be updated on a separate thread
- LikeObjcSync.synced(self) {
+ viewControllerispatchQueue.sync {
percentComplete = Int((simulationTime / config.simDuration) * 100)
}
diff --git a/Foil/LikeObjcSync.swift b/Foil/LikeObjcSync.swift
index 22c9310..59bfe17 100644
--- a/Foil/LikeObjcSync.swift
+++ b/Foil/LikeObjcSync.swift
@@ -1,6 +1,6 @@
import Foundation
-class LikeObjcSync {
+class ikeObjcSync {
public class func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
diff --git a/Renderer/FoilMathUtilities.swift b/Renderer/FoilMathUtilities.swift
index 4dc3b7b..7b8b259 100644
--- a/Renderer/FoilMathUtilities.swift
+++ b/Renderer/FoilMathUtilities.swift
@@ -1,34 +1,46 @@
import simd
-func generate_random_vector(min: Float, max: Float) -> vector_float3 {
- let range = max - min;
+enum FoilMath {
+ static func generateRandomNormalizedVector(_ min: Float, _ max: Float, _ maxlength: Float) -> vector_float3 {
+ var rand = vector_float3()
- let x = Float.random(in: 0..<1) * range + min;
- let y = Float.random(in: 0..<1) * range + min;
- let z = Float.random(in: 0..<1) * range + min;
+ repeat {
+ rand = generateRandomVector(min, max);
+ } while(simd_length(rand) > maxlength)
- return vector_float3(x, y, z)
-}
+ return simd_normalize(rand)
+ }
-func matrix_ortho_left_hand(
- left: Float, right: Float, bottom: Float, top: Float, nearZ: Float, farZ: Float
-) -> matrix_float4x4 {
- return matrix_make_rows(
- 2 / (right - left), 0, 0, (left + right) / (left - right),
- 0, 2 / (top - bottom), 0, (top + bottom) / (bottom - top),
- 0, 0, 1 / (farZ - nearZ), nearZ / (nearZ - farZ),
- 0, 0, 0, 1 );
-}
+ static func generateRandomVector(_ min: Float, _ max: Float) -> vector_float3 {
+ let range = max - min;
+
+ let x = Float.random(in: 0..<1) * range + min;
+ let y = Float.random(in: 0..<1) * range + min;
+ let z = Float.random(in: 0..<1) * range + min;
+
+ return vector_float3(x, y, z)
+ }
+
+ static func matrixOrthoLeftHand(
+ left: Float, right: Float, bottom: Float, top: Float, nearZ: Float, farZ: Float
+ ) -> matrix_float4x4 {
+ return matriMakeRows(
+ 2 / (right - left), 0, 0, (left + right) / (left - right),
+ 0, 2 / (top - bottom), 0, (top + bottom) / (bottom - top),
+ 0, 0, 1 / (farZ - nearZ), nearZ / (nearZ - farZ),
+ 0, 0, 0, 1 );
+ }
-func matrix_make_rows(
- _ m00: Float, _ m10: Float, _ m20: Float, _ m30: Float,
- _ m01: Float, _ m11: Float, _ m21: Float, _ m31: Float,
- _ m02: Float, _ m12: Float, _ m22: Float, _ m32: Float,
- _ m03: Float, _ m13: Float, _ m23: Float, _ m33: Float
-) -> matrix_float4x4 {
- return matrix_float4x4([
- [ m00, m01, m02, m03 ], // each line here provides column data
- [ m10, m11, m12, m13 ],
- [ m20, m21, m22, m23 ],
- [ m30, m31, m32, m33 ] ] )
+ static func matriMakeRows(
+ _ m00: Float, _ m10: Float, _ m20: Float, _ m30: Float,
+ _ m01: Float, _ m11: Float, _ m21: Float, _ m31: Float,
+ _ m02: Float, _ m12: Float, _ m22: Float, _ m32: Float,
+ _ m03: Float, _ m13: Float, _ m23: Float, _ m33: Float
+ ) -> matrix_float4x4 {
+ return matrix_float4x4([
+ [ m00, m01, m02, m03 ], // each line here provides column data
+ [ m10, m11, m12, m13 ],
+ [ m20, m21, m22, m23 ],
+ [ m30, m31, m32, m33 ] ] )
+ }
}
diff --git a/Renderer/FoilRenderer.swift b/Renderer/FoilRenderer.swift
index e997602..b16c899 100644
--- a/Renderer/FoilRenderer.swift
+++ b/Renderer/FoilRenderer.swift
@@ -2,22 +2,18 @@ import Foundation
import MetalKit
class FoilRenderer: NSObject {
- static let maxConcurrentRenderBuffers = 3
-
- // The point size (in pixels) of rendered bodied
+ // The point size (in pixels) of rendered bodies
static let bodyPointSize: Float = 15;
// Size of gaussian map to create rounded smooth points
static let GaussianMapSize = 64
- let pipelineThrottle: DispatchSemaphore
-
let gaussianMap: MTLTexture
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
- var colors: MTLBuffer?
+ var colorsBuffer: MTLBuffer?
// Metal objects
var depthState: MTLDepthStencilState!
@@ -33,16 +29,17 @@ class FoilRenderer: NSObject {
var renderScale: Float = 0
+ let rendererDispatchQueue = DispatchQueue(
+ label: "renderer.q", qos: .default, attributes: [/*serial*/],
+ target: DispatchQueue.global(qos: .default)
+ )
+
/// Initialize with the MetalKit view with the Metal device used to render. This MetalKit view
/// object will also be used to set the pixelFormat and other properties of the drawable
init(_ mtkView: MTKView) {
self.device = mtkView.device!
self.commandQueue = self.device.makeCommandQueue()!
- self.pipelineThrottle = DispatchSemaphore(
- value: FoilRenderer.maxConcurrentRenderBuffers
- )
-
self.gaussianMap = FoilRenderer.generateGaussianMap(self.device)
super.init()
@@ -60,18 +57,8 @@ class FoilRenderer: NSObject {
numBodies: Int,
view: MTKView
) {
- // Limit the action in the pipeline; see whether we can increase
- // the max; the original code is kind of old
- pipelineThrottle.wait()
-
commandBuffer.pushDebugGroup("Draw Simulation Data")
- // Add completion hander which signals pipelineThrottle when Metal and the GPU has fully
- // finished processing the commands encoded this frame. This indicates when the dynamic
- // buffers, written to this frame, will no longer be needed by Metal and the GPU, meaning the
- // buffer contents can be changed without corrupting rendering
- commandBuffer.addCompletedHandler { [weak self] _ in self?.pipelineThrottle.signal() }
-
setNumRenderBodies(numBodies)
updateState()
@@ -89,14 +76,14 @@ class FoilRenderer: NSObject {
precondition(positionsBuffer.length > 0)
// Synchronize since positions buffer may be created on another thread
- LikeObjcSync.synced(self) {
+ rendererDispatchQueue.sync {
renderEncoder.setVertexBuffer(
positionsBuffer, offset: 0, index: FoilRenderBufferIndex.positions.rawValue
)
}
renderEncoder.setVertexBuffer(
- colors, offset: 0, index: FoilRenderBufferIndex.colors.rawValue
+ colorsBuffer, offset: 0, index: FoilRenderBufferIndex.colors.rawValue
)
renderEncoder.setVertexBuffer(
@@ -218,23 +205,19 @@ class FoilRenderer: NSObject {
depthStateDesc.isDepthWriteEnabled = true;
depthState = device.makeDepthStencilState(descriptor: depthStateDesc)
- // Create and allocate the dynamic uniform buffer objects.
- for i in 0...stride
- guard let dub = device.makeBuffer(length: stride, options: storageMode)
- else { fatalError() }
+ // Indicate shared storage so that both the CPU can access the buffers
+ let storageMode = MTLResourceOptions.storageModeShared
+ let stride = MemoryLayout.stride
+ guard let dub = device.makeBuffer(length: stride, options: storageMode)
+ else { fatalError() }
- dub.label = "UniformBuffer\(i)"
- dynamicUniformBuffers.append(dub)
- }
+ dub.label = "UniformBuffer"
+ dynamicUniformBuffers.append(dub)
// Initialize number of bodies to render
setNumRenderBodies(64 * 1024)
commandQueue = device.makeCommandQueue()
-
}
/// Update any render state (including updating dynamically changing Metal buffers)
@@ -247,7 +230,7 @@ class FoilRenderer: NSObject {
}
func providePositionData(data: NSData) {
- LikeObjcSync.synced(self) {
+ rendererDispatchQueue.sync {
// Cast from 'const void *' to 'void *' which is okay in this case since updateData was
// created with -[NSData initWithBytesNoCopy:length:deallocator:] and underlying memory was
// allocated with vm_allocate
@@ -267,28 +250,28 @@ class FoilRenderer: NSObject {
}
func setNumRenderBodies(_ numBodies: Int) {
- if colors == nil || ((colors!.length / MemoryLayout.stride) < numBodies) {
+ if colorsBuffer == nil || ((colorsBuffer!.length / MemoryLayout.stride) < numBodies) {
// If the number of colors stored is less than the number of bodies, recreate the color buffer
- let bufferSize = numBodies * MemoryLayout.stride
+ let bufferSize = numBodies * MemoryLayout.stride
- colors = device.makeBuffer(length: bufferSize, options: .storageModeManaged)
+ colorsBuffer = device.makeBuffer(length: bufferSize, options: .storageModeManaged)
- colors!.label = "Colors";
+ colorsBuffer!.label = "Colors";
- let contents = colors!.contents().bindMemory(to: vector_uchar4.self, capacity: numBodies)
+ let colors = colorsBuffer!.contents().bindMemory(to: vector_uchar4.self, capacity: numBodies)
for i in 0.. Float { return simd_length(vector) }
- static func vector_normalize(_ vector: vector_float3) -> vector_float3 { return simd_normalize(vector) }
-
- /// Utility function providing a random with which to initialize simulation
- static func generate_random_normalized_vector(min: Float, max: Float, maxlength: Float) -> vector_float3 {
- var rand = vector_float3()
-
- repeat {
- rand = generate_random_vector(min: min, max: max);
- } while(vector_length(rand) > maxlength)
-
- return vector_normalize(rand)
- }
-
/// Initialize Metal objects and set simulation parameters
func createMetalObjectsAndMemory() {
// Create compute pipeline for simulation
@@ -250,14 +247,6 @@ class FoilSimulation {
return (buffer, data)
}
- static func vector_dot(_ nrpos: vector_float3, _ axis: vector_float3) -> Float {
- return simd_dot(nrpos, axis)
- }
-
- static func vector_cross(_ position: vector_float3, _ axis: vector_float3) -> vector_float3 {
- return simd_cross(position, axis)
- }
-
/// Set the initial positions and velocities of the simulation based upon the simulation's config
func initializeData() {
let pscale = config.clusterScale
@@ -273,25 +262,25 @@ class FoilSimulation {
let velocities = self.velocities[oldBufferIndex].contents().assumingMemoryBound(to: vector_float4.self)
for i in 0.. Void
) {
- commandBuffer.addCompletedHandler { _ in
- print("runAsyncLoopWithUpdateHandler.6(\(loopCounter))")
- updateHandler()
- print("runAsyncLoopWithUpdateHandler.7(\(loopCounter))")
- }
+ commandBuffer.addCompletedHandler { _ in updateHandler() }
}
}