diff --git a/src/chunk.zig b/src/chunk.zig index 1f0e49cb..164ee8fe 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -93,7 +93,7 @@ pub const Neighbors = struct { // TODO: Should this be an enum? }; /// Gets the index of a given position inside this chunk. -fn getIndex(x: i32, y: i32, z: i32) u32 { +pub fn getIndex(x: i32, y: i32, z: i32) u32 { std.debug.assert((x & chunkMask) == x and (y & chunkMask) == y and (z & chunkMask) == z); return (@as(u32, @intCast(x)) << chunkShift) | (@as(u32, @intCast(y)) << chunkShift2) | @as(u32, @intCast(z)); } @@ -609,12 +609,12 @@ pub const meshing = struct { const z = (wz >> mesh.chunk.voxelSizeShift) & chunkMask; const index = getIndex(x, y, z); return .{ - mesh.lightingData.*[0].data[index], - mesh.lightingData.*[1].data[index], - mesh.lightingData.*[2].data[index], - mesh.lightingData.*[3].data[index], - mesh.lightingData.*[4].data[index], - mesh.lightingData.*[5].data[index], + mesh.lightingData.*[0].data[index].load(.Unordered), + mesh.lightingData.*[1].data[index].load(.Unordered), + mesh.lightingData.*[2].data[index].load(.Unordered), + mesh.lightingData.*[3].data[index].load(.Unordered), + mesh.lightingData.*[4].data[index].load(.Unordered), + mesh.lightingData.*[5].data[index].load(.Unordered), }; } @@ -812,9 +812,12 @@ pub const meshing = struct { pub fn init(self: *ChunkMesh, pos: ChunkPosition, chunk: *Chunk) !void { const lightingData = try main.globalAllocator.create([6]lighting.ChannelChunk); - for(lightingData) |*lightChunk| { - try lightChunk.init(chunk); - } + try lightingData[0].init(chunk, .sun_red); + try lightingData[1].init(chunk, .sun_green); + try lightingData[2].init(chunk, .sun_blue); + try lightingData[3].init(chunk, .red); + try lightingData[4].init(chunk, .green); + try lightingData[5].init(chunk, .blue); self.* = ChunkMesh{ .pos = pos, .size = chunkSize*pos.voxelSize, @@ -853,7 +856,7 @@ pub const meshing = struct { const prevVal = self.refCount.fetchSub(1, .Monotonic); std.debug.assert(prevVal != 0); if(prevVal == 1) { - renderer.RenderStructure.addMeshToClearList(self) catch @panic("Out of Memory"); + renderer.RenderStructure.addMeshToClearListAndDecreaseRefCount(self) catch @panic("Out of Memory"); } } @@ -886,6 +889,8 @@ pub const meshing = struct { self.mutex.lock(); self.opaqueMesh.reset(); self.transparentMesh.reset(); + var lightEmittingBlocks = std.ArrayList([3]u8).init(main.globalAllocator); + defer lightEmittingBlocks.deinit(); var n: u32 = 0; var x: u8 = 0; while(x < chunkSize): (x += 1) { @@ -894,6 +899,7 @@ pub const meshing = struct { var z: u8 = 0; while(z < chunkSize): (z += 1) { const block = (&self.chunk.blocks)[getIndex(x, y, z)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + if(block.light() != 0) try lightEmittingBlocks.append(.{x, y, z}); if(block.typ == 0) continue; // Check all neighbors: for(Neighbors.iterable) |i| { @@ -931,6 +937,10 @@ pub const meshing = struct { } } self.mutex.unlock(); + for(self.lightingData[3..]) |*lightingData| { + try lightingData.propagateLights(lightEmittingBlocks.items, true); + } + // TODO: Sunlight propagation try self.finishNeighbors(false); } @@ -1071,7 +1081,7 @@ pub const meshing = struct { self.transparentMesh.clearNeighbor(neighbor, isLod); } - fn finishData(self: *ChunkMesh) !void { + pub fn finishData(self: *ChunkMesh) !void { std.debug.assert(!self.mutex.tryLock()); try self.opaqueMesh.finish(self); try self.transparentMesh.finish(self); @@ -1167,7 +1177,7 @@ pub const meshing = struct { try neighborMesh.uploadData(); } else { neighborMesh.increaseRefCount(); - try renderer.RenderStructure.addToUpdateList(neighborMesh); + try renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(neighborMesh); } } else { self.mutex.lock(); diff --git a/src/lighting.zig b/src/lighting.zig index 31c91a18..b5bbd21c 100644 --- a/src/lighting.zig +++ b/src/lighting.zig @@ -1,31 +1,164 @@ const std = @import("std"); +const Atomic = std.atomic.Value; const main = @import("root"); const blocks = main.blocks; const chunk = main.chunk; -const Channel = enum { - red, - green, - blue, +const Channel = enum(u8) { + sun_red = 0, + sun_green = 1, + sun_blue = 2, + red = 3, + green = 4, + blue = 5, pub fn shift(self: Channel) u5 { switch(self) { - .red => return 16, - .green => return 8, - .blue => return 0, + .red, .sun_red => return 16, + .green, .sun_green => return 8, + .blue, .sun_blue => return 0, } } }; pub const ChannelChunk = struct { - data: [chunk.chunkVolume]u8, + data: [chunk.chunkVolume]Atomic(u8), mutex: std.Thread.Mutex, + ch: *chunk.Chunk, + channel: Channel, - pub fn init(self: *ChannelChunk, ch: *chunk.Chunk) !void { + pub fn init(self: *ChannelChunk, ch: *chunk.Chunk, channel: Channel) !void { self.mutex = .{}; - for(&self.data, 0..) |*val, i| { - val.* = if(ch.blocks[i].transparent()) 255 else 0; // TODO: Proper light propagation. This is just ambient occlusion at the moment. + self.ch = ch; + self.channel = channel; + @memset(&self.data, Atomic(u8).init(0)); + } + + const Entry = struct { + x: u5, + y: u5, + z: u5, + value: u8, + + fn compare(_: void, a: @This(), b: @This()) std.math.Order { + if (a.value > b.value) return .gt; + if (a.value < b.value) return .lt; + return .eq; + } + }; + + fn propagateDirect(self: *ChannelChunk, lightQueue: *std.PriorityQueue(Entry, void, Entry.compare)) std.mem.Allocator.Error!void { + var neighborLists: [6]std.ArrayListUnmanaged(Entry) = .{.{}} ** 6; + defer { + for(&neighborLists) |*list| { + list.deinit(main.globalAllocator); + } + } + + self.mutex.lock(); + errdefer self.mutex.unlock(); + while(lightQueue.removeOrNull()) |entry| { + const index = chunk.getIndex(entry.x, entry.y, entry.z); + if(entry.value <= self.data[index].load(.Unordered)) continue; + self.data[index].store(entry.value, .Unordered); + for(chunk.Neighbors.iterable) |neighbor| { + const nx = entry.x + chunk.Neighbors.relX[neighbor]; + const ny = entry.y + chunk.Neighbors.relY[neighbor]; + const nz = entry.z + chunk.Neighbors.relZ[neighbor]; + var result: Entry = .{.x = @intCast(nx & chunk.chunkMask), .y = @intCast(ny & chunk.chunkMask), .z = @intCast(nz & chunk.chunkMask), .value = entry.value}; + result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + if(result.value == 0) continue; + if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { + try neighborLists[neighbor].append(main.globalAllocator, result); + continue; + } + const neighborIndex = chunk.getIndex(nx, ny, nz); + var absorption: u8 = @intCast(self.ch.blocks[neighborIndex].absorption() >> self.channel.shift() & 255); + absorption *|= @intCast(self.ch.pos.voxelSize); + result.value -|= absorption; + if(result.value != 0) try lightQueue.add(result); + } + } + self.mutex.unlock(); + if(main.renderer.RenderStructure.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { + mesh.mutex.lock(); + defer mesh.mutex.unlock(); + try mesh.finishData(); + try main.renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(mesh); + } + + for(0..6) |neighbor| { + if(neighborLists[neighbor].items.len == 0) continue; + const neighborMesh = main.renderer.RenderStructure.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, @intCast(neighbor)) orelse continue; + defer neighborMesh.decreaseRefCount(); + try neighborMesh.lightingData[@intFromEnum(self.channel)].propagateFromNeighbor(neighborLists[neighbor].items); } } -}; \ No newline at end of file + + fn propagateFromNeighbor(self: *ChannelChunk, lights: []Entry) std.mem.Allocator.Error!void { + var lightQueue = std.PriorityQueue(Entry, void, Entry.compare).init(main.globalAllocator, {}); + defer lightQueue.deinit(); + for(lights) |entry| { + const index = chunk.getIndex(entry.x, entry.y, entry.z); + var result = entry; + var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255); + absorption *|= @intCast(self.ch.pos.voxelSize); + result.value -|= absorption; + if(result.value != 0) try lightQueue.add(result); + } + try self.propagateDirect(&lightQueue); + } + + pub fn propagateLights(self: *ChannelChunk, lights: [][3]u8, comptime checkNeighbors: bool) std.mem.Allocator.Error!void { + var lightQueue = std.PriorityQueue(Entry, void, Entry.compare).init(main.globalAllocator, {}); + defer lightQueue.deinit(); + for(lights) |pos| { + const index = chunk.getIndex(pos[0], pos[1], pos[2]); + try lightQueue.add(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)}); + } + if(checkNeighbors) { + for(0..6) |neighbor| { + const x3: i32 = if(neighbor & 1 == 0) chunk.chunkMask else 0; + var x1: i32 = 0; + while(x1 < chunk.chunkSize): (x1 += 1) { + var x2: i32 = 0; + while(x2 < chunk.chunkSize): (x2 += 1) { + var x: i32 = undefined; + var y: i32 = undefined; + var z: i32 = undefined; + if(chunk.Neighbors.relX[neighbor] != 0) { + x = x3; + y = x1; + z = x2; + } else if(chunk.Neighbors.relY[neighbor] != 0) { + x = x1; + y = x3; + z = x2; + } else { + x = x2; + y = x1; + z = x3; + } + const otherX = x+%chunk.Neighbors.relX[neighbor] & chunk.chunkMask; + const otherY = y+%chunk.Neighbors.relY[neighbor] & chunk.chunkMask; + const otherZ = z+%chunk.Neighbors.relZ[neighbor] & chunk.chunkMask; + const neighborMesh = main.renderer.RenderStructure.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, @intCast(neighbor)) orelse continue; + defer neighborMesh.decreaseRefCount(); + const neighborLightChunk = &neighborMesh.lightingData[@intFromEnum(self.channel)]; + const index = chunk.getIndex(x, y, z); + const neighborIndex = chunk.getIndex(otherX, otherY, otherZ); + var value: u8 = neighborLightChunk.data[neighborIndex].load(.Unordered); + value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); + if(value == 0) continue; + var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255); + absorption *|= @intCast(self.ch.pos.voxelSize); + value -|= absorption; + if(value != 0) try lightQueue.add(.{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .value = value}); + } + } + } + } + try self.propagateDirect(&lightQueue); + } +}; diff --git a/src/renderer.zig b/src/renderer.zig index 2e33f0ee..8c8af9be 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -1601,14 +1601,14 @@ pub const RenderStructure = struct { } } - pub fn addMeshToClearList(mesh: *chunk.meshing.ChunkMesh) !void { + pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk.meshing.ChunkMesh) !void { std.debug.assert(mesh.refCount.load(.Monotonic) == 0); mutex.lock(); defer mutex.unlock(); try clearList.append(mesh); } - pub fn addToUpdateList(mesh: *chunk.meshing.ChunkMesh) !void { + pub fn addToUpdateListAndDecreaseRefCount(mesh: *chunk.meshing.ChunkMesh) !void { std.debug.assert(mesh.refCount.load(.Monotonic) != 0); mutex.lock(); defer mutex.unlock();