implement parallel chunk mesh building

This commit is contained in:
Alex Zenla 2024-09-03 09:48:29 -04:00
parent 94ae0c26b1
commit cfb985cff6
No known key found for this signature in database
GPG Key ID: 067B238899B51269
4 changed files with 55 additions and 9 deletions

View File

@ -54,6 +54,7 @@ add_executable(Voxelotl MACOSX_BUNDLE
# Game logic classes
Chunk.swift
ChunkGeneration.swift
ChunkMeshGeneration.swift
WorldGenerator.swift
CubeMeshBuilder.swift
ChunkMeshBuilder.swift

View File

@ -1,7 +1,5 @@
struct ChunkMeshBuilder {
public static func build(world: World, chunkID: SIMD3<Int>) -> Mesh<VertexPositionNormalColorTexcoord, UInt16> {
guard let chunk = world.getChunk(id: chunkID) else { return .empty }
public static func build(world: World, chunk: Chunk) -> Mesh<VertexPositionNormalColorTexcoord, UInt16> {
var vertices = [VertexPositionNormalColorTexcoord]()
var indices = [UInt16]()
chunk.forEach { block, position in

View File

@ -0,0 +1,47 @@
import Foundation
public struct ChunkMeshGeneration {
private let queue: OperationQueue
private let localReadyMeshes = ConcurrentDictionary<SIMD3<Int>, RendererMesh>()
weak var game: Game?
weak var renderer: Renderer?
init(queue: DispatchQueue) {
self.queue = OperationQueue()
self.queue.underlyingQueue = queue
self.queue.maxConcurrentOperationCount = 8
self.queue.qualityOfService = .userInitiated
}
public mutating func generate(chunkID: SIMD3<Int>, chunk: Chunk) {
self.queueGenerateJob(chunkID: chunkID, chunk: chunk)
}
func queueGenerateJob(chunkID: SIMD3<Int>, chunk: Chunk) {
self.queue.addOperation {
guard let game = self.game else {
return
}
guard let renderer = self.renderer else {
return
}
let mesh = ChunkMeshBuilder.build(world: game.world, chunk: chunk)
self.localReadyMeshes[chunkID] = renderer.createMesh(mesh)
}
}
public mutating func acceptReadyMeshes() {
guard let game = self.game else {
return
}
queue.waitUntilAllOperationsAreFinished()
for (chunkID, mesh) in self.localReadyMeshes.take() {
game.renderChunks.updateValue(mesh, forKey: chunkID)
}
}
}

View File

@ -27,6 +27,7 @@ class Game: GameDelegate {
var world = World()
var cubeMesh: RendererMesh?
var renderChunks = [SIMD3<Int>: RendererMesh]()
var chunkMeshGeneration: ChunkMeshGeneration!
func create(_ renderer: Renderer) {
self.resetPlayer()
@ -35,6 +36,9 @@ class Game: GameDelegate {
self.cubeMesh = renderer.createMesh(CubeMeshBuilder.build(bound: .fromUnitCube(position: .zero, scale: .one)))
renderer.clearColor = Color<Double>.black.mix(.white, 0.1).linear
self.chunkMeshGeneration = .init(queue: .global(qos: .userInitiated))
self.chunkMeshGeneration.game = self
self.chunkMeshGeneration.renderer = renderer
}
private func resetPlayer() {
@ -110,13 +114,9 @@ class Game: GameDelegate {
// Update chunk meshes if needed
self.world.handleRenderDamagedChunks { id, chunk in
let mesh = ChunkMeshBuilder.build(world: self.world, chunkID: id)
if let renderMesh = renderer.createMesh(mesh) {
self.renderChunks[id] = renderMesh
} else {
self.renderChunks.removeValue(forKey: id)
}
self.chunkMeshGeneration.generate(chunkID: id, chunk: chunk)
}
self.chunkMeshGeneration.acceptReadyMeshes()
for (id, chunk) in self.renderChunks {
let drawPos = SIMD3<Float>(id &<< Chunk.shift)