Implement progress tracking

This commit is contained in:
Eryn Wells 2018-11-18 21:15:20 -07:00
parent 58f59baaf5
commit 2b2539d7df
5 changed files with 116 additions and 26 deletions

View file

@ -25,7 +25,7 @@ protocol TerrainGenerator {
func updateUniforms() func updateUniforms()
func encode(in encoder: MTLComputeCommandEncoder) func encode(in encoder: MTLComputeCommandEncoder)
func render(completion: @escaping () -> Void) func render(progress: Progress) -> [Float]
} }
class Kernel { class Kernel {
@ -99,7 +99,9 @@ class ZeroAlgorithm: Kernel, TerrainGenerator {
func updateUniforms() { } func updateUniforms() { }
func render(completion: @escaping () -> Void) { } func render(progress: Progress) -> [Float] {
return []
}
} }
/// Randomly generate heights that are independent of all others. /// Randomly generate heights that are independent of all others.
@ -133,7 +135,9 @@ class RandomAlgorithm: Kernel, TerrainGenerator {
RandomAlgorithmUniforms_refreshRandoms(uniforms) RandomAlgorithmUniforms_refreshRandoms(uniforms)
} }
func render(completion: @escaping () -> Void) { } func render(progress: Progress) -> [Float] {
return []
}
} }
/// Implementation of the Diamond-Squares algorithm. /// Implementation of the Diamond-Squares algorithm.
@ -224,12 +228,19 @@ public class DiamondSquareGenerator: TerrainGenerator {
] ]
} }
func breadthFirstSearch(visit: (Box) -> (Void)) { func breadthFirstSearch(progress: Progress, visit: (Box) -> (Void)) {
var queue = Queue<Box>() var queue = Queue<Box>()
queue.enqueue(item: self) queue.enqueue(item: self)
progress.totalUnitCount += 1
while let box = queue.dequeue() { while let box = queue.dequeue() {
visit(box) visit(box)
queue.enqueue(items: box.subdivisions) progress.completedUnitCount += 1
let subdivisions = box.subdivisions
queue.enqueue(items: subdivisions)
progress.totalUnitCount += Int64(subdivisions.count)
} }
} }
} }
@ -237,8 +248,6 @@ public class DiamondSquareGenerator: TerrainGenerator {
struct Algorithm { struct Algorithm {
let grid: Box let grid: Box
private let queue = DispatchQueue(label: "Diamond-Square Algorithm Queue")
var roughness: Float = 1.0 { var roughness: Float = 1.0 {
didSet { didSet {
randomRange = -roughness...roughness randomRange = -roughness...roughness
@ -253,14 +262,9 @@ public class DiamondSquareGenerator: TerrainGenerator {
} }
/// Run the algorithm and return the genreated height map. /// Run the algorithm and return the genreated height map.
func render(completion: @escaping ([Float]) -> Void) { func render(progress: Progress) -> [Float] {
queue.async { let renderProgress = Progress(totalUnitCount: 1, parent: progress, pendingUnitCount: 1)
let heightMap = self.queue_render()
completion(heightMap)
}
}
func queue_render() -> [Float] {
os_signpost(.begin, log: Log, name: "DiamondSquare.render") os_signpost(.begin, log: Log, name: "DiamondSquare.render")
var heightMap = [Float](repeating: 0, count: grid.size.w * grid.size.h) var heightMap = [Float](repeating: 0, count: grid.size.w * grid.size.h)
@ -270,8 +274,9 @@ public class DiamondSquareGenerator: TerrainGenerator {
let idx = convert(pointToIndex: p) let idx = convert(pointToIndex: p)
heightMap[idx] = Float.random(in: randomRange) heightMap[idx] = Float.random(in: randomRange)
} }
renderProgress.completedUnitCount += 1
grid.breadthFirstSearch { (box: Box) in grid.breadthFirstSearch(progress: renderProgress) { (box: Box) in
// 1. Diamond step. Find the midpoint of the square defined by `box` and set its value. // 1. Diamond step. Find the midpoint of the square defined by `box` and set its value.
let midpoint = box.midpoint let midpoint = box.midpoint
let cornerValues = box.corners.map { (pt: Point) -> Float in let cornerValues = box.corners.map { (pt: Point) -> Float in
@ -366,15 +371,20 @@ public class DiamondSquareGenerator: TerrainGenerator {
algorithm = Algorithm(grid: Box(origin: Point(), size: Size(w: DiamondSquareGenerator.textureSize.width, h: DiamondSquareGenerator.textureSize.height))) algorithm = Algorithm(grid: Box(origin: Point(), size: Size(w: DiamondSquareGenerator.textureSize.width, h: DiamondSquareGenerator.textureSize.height)))
} }
func render(completion: @escaping () -> Void) { func render(progress: Progress) -> [Float] {
algorithm.render { (heightMap: [Float]) in let algProgress = Progress(totalUnitCount: 2, parent: progress, pendingUnitCount: 2)
let region = MTLRegion(origin: MTLOrigin(), size: DiamondSquareGenerator.textureSize)
let newActiveTexture = (self.activeTexture + 1) % self.textures.count
self.textures[newActiveTexture].replace(region: region, mipmapLevel: 0, withBytes: heightMap, bytesPerRow: MemoryLayout<Float>.stride * DiamondSquareGenerator.textureSize.width)
self.activeTexture = newActiveTexture
completion() let heights = algorithm.render(progress: algProgress)
} algProgress.completedUnitCount += 1
// Swap the active texture to the new one. Copy the height map into the new texture.
let region = MTLRegion(origin: MTLOrigin(), size: DiamondSquareGenerator.textureSize)
let newActiveTexture = (self.activeTexture + 1) % self.textures.count
self.textures[newActiveTexture].replace(region: region, mipmapLevel: 0, withBytes: heights, bytesPerRow: MemoryLayout<Float>.stride * DiamondSquareGenerator.textureSize.width)
self.activeTexture = newActiveTexture
algProgress.completedUnitCount += 1
return heights
} }
// MARK: Algorithm // MARK: Algorithm

View file

@ -14,6 +14,9 @@ class GameViewController: NSViewController {
var renderer: Renderer! var renderer: Renderer!
var mtkView: MTKView! var mtkView: MTKView!
var progressIndicator: NSProgressIndicator!
private var progressObservation: NSKeyValueObservation?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -41,6 +44,17 @@ class GameViewController: NSViewController {
renderer.mtkView(mtkView, drawableSizeWillChange: mtkView.drawableSize) renderer.mtkView(mtkView, drawableSizeWillChange: mtkView.drawableSize)
mtkView.delegate = renderer mtkView.delegate = renderer
progressIndicator = NSProgressIndicator()
progressIndicator.isIndeterminate = false
progressIndicator.minValue = 0.0
progressIndicator.maxValue = 1.0
self.view.addSubview(progressIndicator)
progressIndicator.translatesAutoresizingMaskIntoConstraints = false
progressIndicator.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
progressIndicator.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
progressIndicator.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40.0).isActive = true
progressIndicator.isHidden = true
} }
override func viewWillAppear() { override func viewWillAppear() {
@ -55,7 +69,23 @@ class GameViewController: NSViewController {
override func keyDown(with event: NSEvent) { override func keyDown(with event: NSEvent) {
switch event.charactersIgnoringModifiers { switch event.charactersIgnoringModifiers {
case .some("n"): case .some("n"):
renderer.scheduleAlgorithmIteration() if let progress = renderer.scheduleAlgorithmIteration() {
progressIndicator.isHidden = false
progressObservation = progress.observe(\.fractionCompleted) { [weak self] (progress: Progress, change: NSKeyValueObservedChange<Double>) in
print("Observing progress change: \(progress.fractionCompleted)")
DispatchQueue.main.async {
guard let strongSelf = self else {
return
}
strongSelf.progressIndicator.doubleValue = progress.fractionCompleted
if progress.isFinished {
// TODO: Delay this.
strongSelf.progressIndicator.isHidden = true
strongSelf.progressObservation = nil
}
}
}
}
case .some("z"): case .some("z"):
renderer.drawLines = !renderer.drawLines renderer.drawLines = !renderer.drawLines
default: default:

View file

@ -166,16 +166,18 @@ class Renderer: NSObject, MTKViewDelegate {
} }
func scheduleAlgorithmIteration() { func scheduleAlgorithmIteration() -> Progress? {
var progress: Progress? = nil
regenerationSemaphore.wait() regenerationSemaphore.wait()
if !terrain.generator.needsGPU { if !terrain.generator.needsGPU {
print("Rendering terrain...") print("Rendering terrain...")
self.terrain.generator.render { progress = self.terrain.generate {
print("Rendering terrain...complete!") print("Rendering terrain...complete!")
self.didUpdateTerrain = true self.didUpdateTerrain = true
} }
} }
regenerationSemaphore.signal() regenerationSemaphore.signal()
return progress
} }
private func updateDynamicBufferState() { private func updateDynamicBufferState() {

View file

@ -73,6 +73,11 @@ kernel void updateGeometryNormals(constant float3 *vertexes [[buffer(GeneratorBu
normals[tid] = normal; normals[tid] = normal;
} }
kernel void updateGeometryVertexNormals()
{
}
#pragma mark - ZeroGenerator #pragma mark - ZeroGenerator
kernel void zeroKernel(texture2d<float, access::write> outTexture [[texture(GeneratorTextureIndexOut)]], kernel void zeroKernel(texture2d<float, access::write> outTexture [[texture(GeneratorTextureIndexOut)]],

View file

@ -69,6 +69,20 @@ class Terrain: NSObject {
return try MTKMesh(mesh:plane, device:device) return try MTKMesh(mesh:plane, device:device)
} }
class func computePipeline(withFunctionNamed name: String, device: MTLDevice, library: MTLLibrary) throws -> MTLComputePipelineState {
guard let function = library.makeFunction(name: name) else {
throw RendererError.badComputeFunction
}
let pipeline = try device.makeComputePipelineState(function: function)
return pipeline
}
private let generatorQueue = DispatchQueue(label: "Terrain Generation Queue")
private let updateHeightsPipeline: MTLComputePipelineState
private let updateSurfaceNormalsPipeline: MTLComputePipelineState
private let updateVertexNormalsPipeline: MTLComputePipelineState
let dimensions: float2 let dimensions: float2
let segments: uint2 let segments: uint2
@ -96,6 +110,35 @@ class Terrain: NSObject {
(gen as DiamondSquareGenerator).roughness = 0.075 (gen as DiamondSquareGenerator).roughness = 0.075
generator = gen generator = gen
do {
updateHeightsPipeline = try Terrain.computePipeline(withFunctionNamed: "updateGeometryHeights", device: device, library: library)
updateSurfaceNormalsPipeline = try Terrain.computePipeline(withFunctionNamed: "updateGeometryNormals", device: device, library: library)
updateVertexNormalsPipeline = try Terrain.computePipeline(withFunctionNamed: "updateGeometryVertexNormals", device: device, library: library)
} catch {
print("Unable to create compute pipelines for terrain geometry updates. Error: \(error)")
return nil
}
super.init() super.init()
} }
func generate(completion: @escaping () -> Void) -> Progress {
let progress = Progress(totalUnitCount: 3)
generatorQueue.async {
progress.becomeCurrent(withPendingUnitCount: 3)
let heights = self.generator.render(progress: progress)
progress.completedUnitCount += 1
// TODO: Store heights
progress.completedUnitCount += 1
// TODO: Compute normals
progress.completedUnitCount += 1
progress.resignCurrent()
completion()
}
return progress
}
} }