From 25e4ba6b847ba4ec7cb695ceed70457c61bddb28 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Mon, 15 Jan 2024 18:55:33 +0100 Subject: [PATCH] Split up chunk.zig and renderer.zig I'm getting tired of scrolling around in those large files. --- src/chunk.zig | 1108 ------------------------------ src/graphics.zig | 24 +- src/gui/windows/debug.zig | 12 +- src/main.zig | 6 - src/network.zig | 8 +- src/renderer.zig | 1083 +----------------------------- src/renderer/chunk_meshing.zig | 1118 +++++++++++++++++++++++++++++++ src/{ => renderer}/lighting.zig | 14 +- src/renderer/mesh_storage.zig | 1058 +++++++++++++++++++++++++++++ 9 files changed, 2231 insertions(+), 2200 deletions(-) create mode 100644 src/renderer/chunk_meshing.zig rename src/{ => renderer}/lighting.zig (93%) create mode 100644 src/renderer/mesh_storage.zig diff --git a/src/chunk.zig b/src/chunk.zig index 2245032d..1198a8eb 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -3,22 +3,11 @@ const Allocator = std.mem.Allocator; const blocks = @import("blocks.zig"); const Block = blocks.Block; -const game = @import("game.zig"); -const graphics = @import("graphics.zig"); -const c = graphics.c; -const Shader = graphics.Shader; -const SSBO = graphics.SSBO; -const lighting = @import("lighting.zig"); const main = @import("main.zig"); -const models = @import("models.zig"); -const renderer = @import("renderer.zig"); const settings = @import("settings.zig"); const vec = @import("vec.zig"); -const Vec2f = vec.Vec2f; const Vec3i = vec.Vec3i; -const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; -const Mat4f = vec.Mat4f; pub const chunkShift: u5 = 5; pub const chunkShift2: u5 = chunkShift*2; @@ -388,1101 +377,4 @@ pub const Chunk = struct { } } } -}; - - -pub const meshing = struct { - var shader: Shader = undefined; - var transparentShader: Shader = undefined; - const UniformStruct = struct { - projectionMatrix: c_int, - viewMatrix: c_int, - modelPosition: c_int, - screenSize: c_int, - ambientLight: c_int, - @"fog.color": c_int, - @"fog.density": c_int, - texture_sampler: c_int, - emissionSampler: c_int, - reflectionMap: c_int, - reflectionMapSize: c_int, - visibilityMask: c_int, - voxelSize: c_int, - zNear: c_int, - zFar: c_int, - }; - pub var uniforms: UniformStruct = undefined; - pub var transparentUniforms: UniformStruct = undefined; - var vao: c_uint = undefined; - var vbo: c_uint = undefined; - var faces: std.ArrayList(u32) = undefined; - pub var faceBuffer: graphics.LargeBuffer(FaceData) = undefined; - pub var quadsDrawn: usize = 0; - pub var transparentQuadsDrawn: usize = 0; - - pub fn init() !void { - shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs", &uniforms); - transparentShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/transparent_fragment.fs", &transparentUniforms); - - var rawData: [6*3 << (3*chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block - const lut = [_]u32{0, 1, 2, 2, 1, 3}; - for(0..rawData.len) |i| { - rawData[i] = @as(u32, @intCast(i))/6*4 + lut[i%6]; - } - - c.glGenVertexArrays(1, &vao); - c.glBindVertexArray(vao); - c.glGenBuffers(1, &vbo); - c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbo); - c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, rawData.len*@sizeOf(u32), &rawData, c.GL_STATIC_DRAW); - c.glBindVertexArray(0); - - faces = try std.ArrayList(u32).initCapacity(main.globalAllocator, 65536); - try faceBuffer.init(main.globalAllocator, 1 << 20, 3); - } - - pub fn deinit() void { - shader.deinit(); - transparentShader.deinit(); - c.glDeleteVertexArrays(1, &vao); - c.glDeleteBuffers(1, &vbo); - faces.deinit(); - faceBuffer.deinit(); - } - - pub fn beginRender() !void { - try faceBuffer.beginRender(); - } - - pub fn endRender() void { - faceBuffer.endRender(); - } - - fn bindCommonUniforms(locations: *UniformStruct, projMatrix: Mat4f, ambient: Vec3f) void { - c.glUniformMatrix4fv(locations.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix)); - - c.glUniform1i(locations.texture_sampler, 0); - c.glUniform1i(locations.emissionSampler, 1); - c.glUniform1i(locations.reflectionMap, 2); - c.glUniform1f(locations.reflectionMapSize, renderer.reflectionCubeMapSize); - - c.glUniformMatrix4fv(locations.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix)); - - c.glUniform3f(locations.ambientLight, ambient[0], ambient[1], ambient[2]); - - c.glUniform1f(locations.zNear, renderer.zNear); - c.glUniform1f(locations.zFar, renderer.zFar); - } - - pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void { - shader.bind(); - - bindCommonUniforms(&uniforms, projMatrix, ambient); - - c.glBindVertexArray(vao); - } - - pub fn bindTransparentShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void { - transparentShader.bind(); - - c.glUniform3fv(transparentUniforms.@"fog.color", 1, @ptrCast(&game.fog.color)); - c.glUniform1f(transparentUniforms.@"fog.density", game.fog.density); - - bindCommonUniforms(&transparentUniforms, projMatrix, ambient); - - c.glBindVertexArray(vao); - } - - pub const FaceData = extern struct { - position: packed struct(u32) { - x: u5, - y: u5, - z: u5, - padding: u4 = 0, - isBackFace: bool, - normal: u3, - permutation: u6, - padding2: u3 = 0, - }, - blockAndModel: packed struct(u32) { - typ: u16, - modelIndex: u16, - }, - light: [4]u32 = .{0, 0, 0, 0}, - }; - - const PrimitiveMesh = struct { - coreFaces: std.ArrayListUnmanaged(FaceData) = .{}, - neighborFacesSameLod: [6]std.ArrayListUnmanaged(FaceData) = [_]std.ArrayListUnmanaged(FaceData){.{}} ** 6, - neighborFacesHigherLod: [6]std.ArrayListUnmanaged(FaceData) = [_]std.ArrayListUnmanaged(FaceData){.{}} ** 6, - completeList: []FaceData = &.{}, - coreLen: u32 = 0, - sameLodLens: [6]u32 = .{0} ** 6, - higherLodLens: [6]u32 = .{0} ** 6, - mutex: std.Thread.Mutex = .{}, - bufferAllocation: graphics.SubAllocation = .{.start = 0, .len = 0}, - vertexCount: u31 = 0, - wasChanged: bool = false, - - fn deinit(self: *PrimitiveMesh) void { - faceBuffer.free(self.bufferAllocation) catch unreachable; - self.coreFaces.deinit(main.globalAllocator); - for(&self.neighborFacesSameLod) |*neighborFaces| { - neighborFaces.deinit(main.globalAllocator); - } - for(&self.neighborFacesHigherLod) |*neighborFaces| { - neighborFaces.deinit(main.globalAllocator); - } - main.globalAllocator.free(self.completeList); - } - - fn reset(self: *PrimitiveMesh) void { - self.coreFaces.clearRetainingCapacity(); - for(&self.neighborFacesSameLod) |*neighborFaces| { - neighborFaces.clearRetainingCapacity(); - } - for(&self.neighborFacesHigherLod) |*neighborFaces| { - neighborFaces.clearRetainingCapacity(); - } - } - - fn appendCore(self: *PrimitiveMesh, face: FaceData) !void { - try self.coreFaces.append(main.globalAllocator, face); - } - - fn appendNeighbor(self: *PrimitiveMesh, face: FaceData, neighbor: u3, comptime isLod: bool) !void { - if(isLod) { - try self.neighborFacesHigherLod[neighbor].append(main.globalAllocator, face); - } else { - try self.neighborFacesSameLod[neighbor].append(main.globalAllocator, face); - } - } - - fn clearNeighbor(self: *PrimitiveMesh, neighbor: u3, comptime isLod: bool) void { - if(isLod) { - self.neighborFacesHigherLod[neighbor].clearRetainingCapacity(); - } else { - self.neighborFacesSameLod[neighbor].clearRetainingCapacity(); - } - } - - fn finish(self: *PrimitiveMesh, parent: *ChunkMesh) !void { - var len: usize = self.coreFaces.items.len; - for(self.neighborFacesSameLod) |neighborFaces| { - len += neighborFaces.items.len; - } - for(self.neighborFacesHigherLod) |neighborFaces| { - len += neighborFaces.items.len; - } - const completeList = try main.globalAllocator.alloc(FaceData, len); - var i: usize = 0; - @memcpy(completeList[i..][0..self.coreFaces.items.len], self.coreFaces.items); - i += self.coreFaces.items.len; - for(self.neighborFacesSameLod) |neighborFaces| { - @memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items); - i += neighborFaces.items.len; - } - for(self.neighborFacesHigherLod) |neighborFaces| { - @memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items); - i += neighborFaces.items.len; - } - for(completeList) |*face| { - face.light = getLight(parent, face.position.x, face.position.y, face.position.z, face.position.normal); - } - self.mutex.lock(); - const oldList = self.completeList; - self.completeList = completeList; - self.coreLen = @intCast(self.coreFaces.items.len); - for(self.neighborFacesSameLod, 0..) |neighborFaces, j| { - self.sameLodLens[j] = @intCast(neighborFaces.items.len); - } - for(self.neighborFacesHigherLod, 0..) |neighborFaces, j| { - self.higherLodLens[j] = @intCast(neighborFaces.items.len); - } - self.mutex.unlock(); - main.globalAllocator.free(oldList); - } - - fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 { - const x = (wx >> mesh.chunk.voxelSizeShift) & chunkMask; - const y = (wy >> mesh.chunk.voxelSizeShift) & chunkMask; - const z = (wz >> mesh.chunk.voxelSizeShift) & chunkMask; - const index = getIndex(x, y, z); - return .{ - 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), - }; - } - - fn getLightAt(parent: *ChunkMesh, x: i32, y: i32, z: i32) [6]u8 { - const wx = parent.pos.wx +% x*parent.pos.voxelSize; - const wy = parent.pos.wy +% y*parent.pos.voxelSize; - const wz = parent.pos.wz +% z*parent.pos.voxelSize; - if(x == x & chunkMask and y == y & chunkMask and z == z & chunkMask) { - return getValues(parent, wx, wy, wz); - } - const neighborMesh = renderer.RenderStructure.getMeshFromAnyLodAndIncreaseRefCount(wx, wy, wz, parent.pos.voxelSize) orelse return .{0, 0, 0, 0, 0, 0}; - defer neighborMesh.decreaseRefCount(); - // TODO: If the neighbor mesh has a higher lod the transition isn't seamless. - return getValues(neighborMesh, wx, wy, wz); - } - - fn getLight(parent: *ChunkMesh, x: i32, y: i32, z: i32, normal: u3) [4]u32 { - // TODO: Add a case for non-full cube models. This requires considering more light values along the normal. - const pos = Vec3i{x, y, z}; - var rawVals: [3][3][6]u8 = undefined; - var dx: i32 = -1; - while(dx <= 1): (dx += 1) { - var dy: i32 = -1; - while(dy <= 1): (dy += 1) { - const lightPos = pos +% Neighbors.textureX[normal]*@as(Vec3i, @splat(dx)) +% Neighbors.textureY[normal]*@as(Vec3i, @splat(dy)); - rawVals[@intCast(dx + 1)][@intCast(dy + 1)] = getLightAt(parent, lightPos[0], lightPos[1], lightPos[2]); - } - } - var interpolatedVals: [6][4]u32 = undefined; - for(0..6) |channel| { - for(0..2) |destX| { - for(0..2) |destY| { - var val: u32 = 0; - for(0..2) |sourceX| { - for(0..2) |sourceY| { - val += rawVals[destX+sourceX][destY+sourceY][channel]; - } - } - interpolatedVals[channel][destX*2 + destY] = @intCast(val >> 2+3); - } - } - } - var result: [4]u32 = undefined; - for(0..4) |i| { - result[i] = ( - interpolatedVals[0][i] << 25 | - interpolatedVals[1][i] << 20 | - interpolatedVals[2][i] << 15 | - interpolatedVals[3][i] << 10 | - interpolatedVals[4][i] << 5 | - interpolatedVals[5][i] << 0 - ); - } - return result; - } - - fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) !void { - self.mutex.lock(); - defer self.mutex.unlock(); - var len: u32 = self.coreLen; - var offset: u32 = self.coreLen; - var list: [6][]FaceData = undefined; - for(0..6) |i| { - const neighborLen = self.sameLodLens[i]; - if(!isNeighborLod[i]) { - list[i] = self.completeList[offset..][0..neighborLen]; - len += neighborLen; - } - offset += neighborLen; - } - for(0..6) |i| { - const neighborLen = self.higherLodLens[i]; - if(isNeighborLod[i]) { - list[i] = self.completeList[offset..][0..neighborLen]; - len += neighborLen; - } - offset += neighborLen; - } - const fullBuffer = try faceBuffer.allocateAndMapRange(len, &self.bufferAllocation); - defer faceBuffer.unmapRange(fullBuffer); - @memcpy(fullBuffer[0..self.coreLen], self.completeList[0..self.coreLen]); - var i: usize = self.coreLen; - for(0..6) |n| { - @memcpy(fullBuffer[i..][0..list[n].len], list[n]); - i += list[n].len; - } - self.vertexCount = @intCast(6*fullBuffer.len); - self.wasChanged = true; - } - - fn addFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) !void { - if(fromNeighborChunk) |neighbor| { - try self.neighborFacesSameLod[neighbor].append(main.globalAllocator, faceData); - } else { - try self.coreFaces.append(main.globalAllocator, faceData); - } - } - - fn removeFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) void { - if(fromNeighborChunk) |neighbor| { - var pos: usize = std.math.maxInt(usize); - for(self.neighborFacesSameLod[neighbor].items, 0..) |item, i| { - if(std.meta.eql(faceData, item)) { - pos = i; - break; - } - } - _ = self.neighborFacesSameLod[neighbor].swapRemove(pos); - } else { - var pos: usize = std.math.maxInt(usize); - for(self.coreFaces.items, 0..) |item, i| { - if(std.meta.eql(faceData, item)) { - pos = i; - break; - } - } - _ = self.coreFaces.swapRemove(pos); - } - } - }; - - pub const ChunkMesh = struct { - const SortingData = struct { - face: FaceData, - distance: u32, - isBackFace: bool, - shouldBeCulled: bool, - - pub fn update(self: *SortingData, chunkDx: i32, chunkDy: i32, chunkDz: i32) void { - const x: i32 = self.face.position.x; - const y: i32 = self.face.position.y; - const z: i32 = self.face.position.z; - const dx = x + chunkDx; - const dy = y + chunkDy; - const dz = z + chunkDz; - const normal = self.face.position.normal; - self.isBackFace = self.face.position.isBackFace; - switch(Neighbors.vectorComponent[normal]) { - .x => { - self.shouldBeCulled = (dx < 0) == (Neighbors.relX[normal] < 0); - if(dx == 0) { - self.shouldBeCulled = false; - } - }, - .y => { - self.shouldBeCulled = (dy < 0) == (Neighbors.relY[normal] < 0); - if(dy == 0) { - self.shouldBeCulled = false; - } - }, - .z => { - self.shouldBeCulled = (dz < 0) == (Neighbors.relZ[normal] < 0); - if(dz == 0) { - self.shouldBeCulled = false; - } - }, - } - const fullDx = dx - Neighbors.relX[normal]; - const fullDy = dy - Neighbors.relY[normal]; - const fullDz = dz - Neighbors.relZ[normal]; - self.distance = @abs(fullDx) + @abs(fullDy) + @abs(fullDz); - } - }; - const BoundingRectToNeighborChunk = struct { - min: Vec3i = @splat(std.math.maxInt(i32)), - max: Vec3i = @splat(0), - - fn adjustToBlock(self: *BoundingRectToNeighborChunk, block: Block, pos: Vec3i, neighbor: u3) void { - if(block.viewThrough()) { - self.min = @min(self.min, pos); - self.max = @max(self.max, pos + Neighbors.orthogonalComponents[neighbor]); - } - } - }; - pos: ChunkPosition, - size: i32, - chunk: *Chunk, - lightingData: *[6]lighting.ChannelChunk, - opaqueMesh: PrimitiveMesh, - transparentMesh: PrimitiveMesh, - lastNeighborsSameLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, - lastNeighborsHigherLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, - isNeighborLod: [6]bool = .{false} ** 6, - visibilityMask: u8 = 0xff, - currentSorting: []SortingData = &.{}, - sortingOutputBuffer: []FaceData = &.{}, - culledSortingCount: u31 = 0, - lastTransparentUpdatePos: Vec3i = Vec3i{0, 0, 0}, - refCount: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), - needsLightRefresh: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), - needsMeshUpdate: bool = false, - finishedMeshing: bool = false, - mutex: std.Thread.Mutex = .{}, - - chunkBorders: [6]BoundingRectToNeighborChunk = [1]BoundingRectToNeighborChunk{.{}} ** 6, - - pub fn init(self: *ChunkMesh, pos: ChunkPosition, chunk: *Chunk) !void { - const lightingData = try main.globalAllocator.create([6]lighting.ChannelChunk); - 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, - .opaqueMesh = .{}, - .transparentMesh = .{}, - .chunk = chunk, - .lightingData = lightingData, - }; - } - - pub fn deinit(self: *ChunkMesh) void { - std.debug.assert(self.refCount.load(.Monotonic) == 0); - self.opaqueMesh.deinit(); - self.transparentMesh.deinit(); - main.globalAllocator.free(self.currentSorting); - main.globalAllocator.free(self.sortingOutputBuffer); - main.globalAllocator.destroy(self.chunk); - main.globalAllocator.destroy(self.lightingData); - } - - pub fn increaseRefCount(self: *ChunkMesh) void { - const prevVal = self.refCount.fetchAdd(1, .Monotonic); - std.debug.assert(prevVal != 0); - } - - /// In cases where it's not certain whether the thing was cleared already. - pub fn tryIncreaseRefCount(self: *ChunkMesh) bool { - var prevVal = self.refCount.load(.Monotonic); - while(prevVal != 0) { - prevVal = self.refCount.cmpxchgWeak(prevVal, prevVal + 1, .Monotonic, .Monotonic) orelse return true; - } - return false; - } - - pub fn decreaseRefCount(self: *ChunkMesh) void { - const prevVal = self.refCount.fetchSub(1, .Monotonic); - std.debug.assert(prevVal != 0); - if(prevVal == 1) { - renderer.RenderStructure.addMeshToClearListAndDecreaseRefCount(self) catch @panic("Out of Memory"); - } - } - - pub fn scheduleLightRefreshAndDecreaseRefCount(self: *ChunkMesh) !void { - if(!self.needsLightRefresh.swap(true, .AcqRel)) { - try LightRefreshTask.scheduleAndDecreaseRefCount(self); - } else { - self.decreaseRefCount(); - } - } - const LightRefreshTask = struct { - mesh: *ChunkMesh, - - pub const vtable = main.utils.ThreadPool.VTable{ - .getPriority = @ptrCast(&getPriority), - .isStillNeeded = @ptrCast(&isStillNeeded), - .run = @ptrCast(&run), - .clean = @ptrCast(&clean), - }; - - pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) !void { - const task = try main.globalAllocator.create(LightRefreshTask); - task.* = .{ - .mesh = mesh, - }; - try main.threadPool.addTask(task, &vtable); - } - - pub fn getPriority(_: *LightRefreshTask) f32 { - return 1000000; - } - - pub fn isStillNeeded(_: *LightRefreshTask) bool { - return true; // TODO: Is it worth checking for this? - } - - pub fn run(self: *LightRefreshTask) Allocator.Error!void { - if(self.mesh.needsLightRefresh.swap(false, .AcqRel)) { - self.mesh.mutex.lock(); - try self.mesh.finishData(); - self.mesh.mutex.unlock(); - try renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(self.mesh); - } else { - self.mesh.decreaseRefCount(); - } - main.globalAllocator.destroy(self); - } - - pub fn clean(self: *LightRefreshTask) void { - self.mesh.decreaseRefCount(); - main.globalAllocator.destroy(self); - } - }; - - pub fn isEmpty(self: *const ChunkMesh) bool { - return self.opaqueMesh.vertexCount == 0 and self.transparentMesh.vertexCount == 0; - } - - fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool { - const rotatedModel = blocks.meshes.model(block); - const model = &models.models.items[rotatedModel.modelIndex]; - const freestandingModel = rotatedModel.modelIndex != models.fullCube and switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) { - Neighbors.dirNegX => model.min[0] != 0, - Neighbors.dirPosX => model.max[0] != 16, // TODO: Use a bitfield inside the models or something like that. - Neighbors.dirDown => model.min[1] != 0, - Neighbors.dirUp => model.max[1] != 16, - Neighbors.dirNegZ => model.min[2] != 0, - Neighbors.dirPosZ => model.max[2] != 16, - else => unreachable, - }; - return block.typ != 0 and ( - freestandingModel - or other.typ == 0 - or (!std.meta.eql(block, other) and other.viewThrough()) - or blocks.meshes.model(other).modelIndex != 0 // TODO: make this more strict to avoid overdraw. - ); - } - - pub fn regenerateMainMesh(self: *ChunkMesh) !void { - try renderer.RenderStructure.addMeshToStorage(self); - 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) { - var y: u8 = 0; - while(y < chunkSize): (y += 1) { - 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| { - n += 1; - const x2 = x + Neighbors.relX[i]; - const y2 = y + Neighbors.relY[i]; - const z2 = z + Neighbors.relZ[i]; - if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk. - const neighborBlock = (&self.chunk.blocks)[getIndex(x2, y2, z2)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. - if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) { - if(block.transparent()) { - if(block.hasBackFace()) { - try self.transparentMesh.appendCore(constructFaceData(block, i ^ 1, x, y, z, true)); - } - try self.transparentMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false)); - } else { - try self.opaqueMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false)); // TODO: Create multiple faces for non-cube meshes. - } - } - } - } - } - } - // Check out the borders: - x = 0; - while(x < chunkSize): (x += 1) { - var y: u8 = 0; - while(y < chunkSize): (y += 1) { - self.chunkBorders[Neighbors.dirNegX].adjustToBlock((&self.chunk.blocks)[getIndex(0, x, y)], .{0, x, y}, Neighbors.dirNegX); // TODO: Wait for the compiler bug to get fixed. - self.chunkBorders[Neighbors.dirPosX].adjustToBlock((&self.chunk.blocks)[getIndex(chunkSize-1, x, y)], .{chunkSize, x, y}, Neighbors.dirPosX); // TODO: Wait for the compiler bug to get fixed. - self.chunkBorders[Neighbors.dirDown].adjustToBlock((&self.chunk.blocks)[getIndex(x, 0, y)], .{x, 0, y}, Neighbors.dirDown); // TODO: Wait for the compiler bug to get fixed. - self.chunkBorders[Neighbors.dirUp].adjustToBlock((&self.chunk.blocks)[getIndex(x, chunkSize-1, y)], .{x, chunkSize, y}, Neighbors.dirUp); // TODO: Wait for the compiler bug to get fixed. - self.chunkBorders[Neighbors.dirNegZ].adjustToBlock((&self.chunk.blocks)[getIndex(x, y, 0)], .{x, y, 0}, Neighbors.dirNegZ); // TODO: Wait for the compiler bug to get fixed. - self.chunkBorders[Neighbors.dirPosZ].adjustToBlock((&self.chunk.blocks)[getIndex(x, y, chunkSize-1)], .{x, y, chunkSize}, Neighbors.dirPosZ); // TODO: Wait for the compiler bug to get fixed. - } - } - self.mutex.unlock(); - for(self.lightingData[3..]) |*lightingData| { - try lightingData.propagateLights(lightEmittingBlocks.items, true); - } - sunLight: { - var sunStarters: [chunkSize*chunkSize][3]u8 = undefined; - var index: usize = 0; - const lightStartMap = renderer.RenderStructure.getLightMapPieceAndIncreaseRefCount(self.pos.wx, self.pos.wz, self.pos.voxelSize) orelse break :sunLight; - defer lightStartMap.decreaseRefCount(); - x = 0; - while(x < chunkSize): (x += 1) { - var z: u8 = 0; - while(z < chunkSize): (z += 1) { - const startHeight: i32 = lightStartMap.getHeight(self.pos.wx + x*self.pos.voxelSize, self.pos.wz + z*self.pos.voxelSize); - const relHeight = startHeight -% self.pos.wy; - if(relHeight < chunkSize*self.pos.voxelSize) { - sunStarters[index] = .{x, chunkSize-1, z}; - index += 1; - } - } - } - for(self.lightingData[0..3]) |*lightingData| { - try lightingData.propagateLights(sunStarters[0..index], true); - } - } - - // TODO: Sunlight propagation - try self.finishNeighbors(false); - } - - fn addFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) !void { - if(transparent) { - try self.transparentMesh.addFace(faceData, fromNeighborChunk); - } else { - try self.opaqueMesh.addFace(faceData, fromNeighborChunk); - } - } - - fn removeFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) void { - if(transparent) { - self.transparentMesh.removeFace(faceData, fromNeighborChunk); - } else { - self.opaqueMesh.removeFace(faceData, fromNeighborChunk); - } - } - - pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, newBlock: Block) !void { - self.mutex.lock(); - const x = _x & chunkMask; - const y = _y & chunkMask; - const z = _z & chunkMask; - const oldBlock = self.chunk.blocks[getIndex(x, y, z)]; - self.chunk.blocks[getIndex(x, y, z)] = newBlock; - self.mutex.unlock(); - for(self.lightingData[0..]) |*lightingData| { - try lightingData.propagateLightsDestructive(&.{.{@intCast(x), @intCast(y), @intCast(z)}}); - } - if(newBlock.light() != 0) { - for(self.lightingData[3..]) |*lightingData| { - try lightingData.propagateLights(&.{.{@intCast(x), @intCast(y), @intCast(z)}}, false); - } - } - self.mutex.lock(); - defer self.mutex.unlock(); - for(Neighbors.iterable) |neighbor| { - var neighborMesh = self; - var nx = x + Neighbors.relX[neighbor]; - var ny = y + Neighbors.relY[neighbor]; - var nz = z + Neighbors.relZ[neighbor]; - if(nx & chunkMask != nx or ny & chunkMask != ny or nz & chunkMask != nz) { // Outside this chunk. - neighborMesh = renderer.RenderStructure.getNeighborFromRenderThread(self.pos, self.pos.voxelSize, neighbor) orelse continue; - } - if(neighborMesh != self) { - self.mutex.unlock(); - deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); - } - defer if(neighborMesh != self) neighborMesh.mutex.unlock(); - nx &= chunkMask; - ny &= chunkMask; - nz &= chunkMask; - const neighborBlock = neighborMesh.chunk.blocks[getIndex(nx, ny, nz)]; - { // TODO: Batch all the changes and apply them in one go for more efficiency. - { // The face of the changed block - const newVisibility = canBeSeenThroughOtherBlock(newBlock, neighborBlock, neighbor); - const oldVisibility = canBeSeenThroughOtherBlock(oldBlock, neighborBlock, neighbor); - if(oldVisibility) { // Removing the face - const faceData = constructFaceData(oldBlock, neighbor, nx, ny, nz, false); - if(neighborMesh == self) { - self.removeFace(faceData, null, oldBlock.transparent()); - } else { - neighborMesh.removeFace(faceData, neighbor ^ 1, oldBlock.transparent()); - } - if(oldBlock.hasBackFace()) { - const backFaceData = constructFaceData(oldBlock, neighbor ^ 1, x, y, z, true); - if(neighborMesh == self) { - self.removeFace(backFaceData, null, true); - } else { - self.removeFace(backFaceData, neighbor, true); - } - } - } - if(newVisibility) { // Adding the face - const faceData = constructFaceData(newBlock, neighbor, nx, ny, nz, false); - if(neighborMesh == self) { - try self.addFace(faceData, null, newBlock.transparent()); - } else { - try neighborMesh.addFace(faceData, neighbor ^ 1, newBlock.transparent()); - } - if(newBlock.hasBackFace()) { - const backFaceData = constructFaceData(newBlock, neighbor ^ 1, x, y, z, true); - if(neighborMesh == self) { - try self.addFace(backFaceData, null, true); - } else { - try self.addFace(backFaceData, neighbor, true); - } - } - } - } - { // The face of the neighbor block - const newVisibility = canBeSeenThroughOtherBlock(neighborBlock, newBlock, neighbor ^ 1); - if(canBeSeenThroughOtherBlock(neighborBlock, oldBlock, neighbor ^ 1) != newVisibility) { - if(newVisibility) { // Adding the face - const faceData = constructFaceData(neighborBlock, neighbor ^ 1, x, y, z, false); - if(neighborMesh == self) { - try self.addFace(faceData, null, neighborBlock.transparent()); - } else { - try self.addFace(faceData, neighbor, neighborBlock.transparent()); - } - if(neighborBlock.hasBackFace()) { - const backFaceData = constructFaceData(neighborBlock, neighbor, nx, ny, nz, true); - if(neighborMesh == self) { - try self.addFace(backFaceData, null, true); - } else { - try neighborMesh.addFace(backFaceData, neighbor ^ 1, true); - } - } - } else { // Removing the face - const faceData = constructFaceData(neighborBlock, neighbor ^ 1, x, y, z, false); - if(neighborMesh == self) { - self.removeFace(faceData, null, neighborBlock.transparent()); - } else { - self.removeFace(faceData, neighbor, neighborBlock.transparent()); - } - if(neighborBlock.hasBackFace()) { - const backFaceData = constructFaceData(neighborBlock, neighbor, nx, ny, nz, true); - if(neighborMesh == self) { - self.removeFace(backFaceData, null, true); - } else { - neighborMesh.removeFace(backFaceData, neighbor ^ 1, true); - } - } - } - } - } - } - if(neighborMesh != self) { - _ = neighborMesh.needsLightRefresh.swap(false, .AcqRel); - try neighborMesh.finishData(); - try neighborMesh.uploadData(); - } - } - _ = self.needsLightRefresh.swap(false, .AcqRel); - try self.finishData(); - try self.uploadData(); - } - - pub inline fn constructFaceData(block: Block, normal: u3, x: i32, y: i32, z: i32, comptime backFace: bool) FaceData { - const model = blocks.meshes.model(block); - return FaceData { - .position = .{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .normal = normal, .permutation = model.permutation.toInt(), .isBackFace = backFace}, - .blockAndModel = .{.typ = block.typ, .modelIndex = model.modelIndex}, - }; - } - - fn clearNeighbor(self: *ChunkMesh, neighbor: u3, comptime isLod: bool) void { - self.opaqueMesh.clearNeighbor(neighbor, isLod); - self.transparentMesh.clearNeighbor(neighbor, isLod); - } - - pub fn finishData(self: *ChunkMesh) !void { - std.debug.assert(!self.mutex.tryLock()); - try self.opaqueMesh.finish(self); - try self.transparentMesh.finish(self); - } - - pub fn uploadData(self: *ChunkMesh) !void { - try self.opaqueMesh.uploadData(self.isNeighborLod); - try self.transparentMesh.uploadData(self.isNeighborLod); - } - - pub fn changeLodBorders(self: *ChunkMesh, isNeighborLod: [6]bool) !void { - if(!std.meta.eql(isNeighborLod, self.isNeighborLod)) { - self.isNeighborLod = isNeighborLod; - try self.uploadData(); - } - } - - fn deadlockFreeDoubleLock(m1: *std.Thread.Mutex, m2: *std.Thread.Mutex) void { - if(@intFromPtr(m1) < @intFromPtr(m2)) { - m1.lock(); - m2.lock(); - } else { - m2.lock(); - m1.lock(); - } - } - - fn finishNeighbors(self: *ChunkMesh, comptime inRenderThread: bool) !void { - const getNeighborMesh: fn(ChunkPosition, u31, u3) ?*ChunkMesh = if(inRenderThread) renderer.RenderStructure.getNeighborFromRenderThread else renderer.RenderStructure.getNeighborAndIncreaseRefCount; - for(Neighbors.iterable) |neighbor| { - const nullNeighborMesh = getNeighborMesh(self.pos, self.pos.voxelSize, neighbor); - if(nullNeighborMesh) |neighborMesh| sameLodBlock: { - defer if(!inRenderThread) neighborMesh.decreaseRefCount(); - std.debug.assert(neighborMesh != self); - deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); - defer self.mutex.unlock(); - defer neighborMesh.mutex.unlock(); - if(self.lastNeighborsSameLod[neighbor] == neighborMesh) break :sameLodBlock; - self.lastNeighborsSameLod[neighbor] = neighborMesh; - neighborMesh.lastNeighborsSameLod[neighbor ^ 1] = self; - self.clearNeighbor(neighbor, false); - neighborMesh.clearNeighbor(neighbor ^ 1, false); - const x3: i32 = if(neighbor & 1 == 0) chunkMask else 0; - var x1: i32 = 0; - while(x1 < chunkSize): (x1 += 1) { - var x2: i32 = 0; - while(x2 < chunkSize): (x2 += 1) { - var x: i32 = undefined; - var y: i32 = undefined; - var z: i32 = undefined; - if(Neighbors.relX[neighbor] != 0) { - x = x3; - y = x1; - z = x2; - } else if(Neighbors.relY[neighbor] != 0) { - x = x1; - y = x3; - z = x2; - } else { - x = x2; - y = x1; - z = x3; - } - const otherX = x+%Neighbors.relX[neighbor] & chunkMask; - const otherY = y+%Neighbors.relY[neighbor] & chunkMask; - const otherZ = z+%Neighbors.relZ[neighbor] & chunkMask; - const block = (&self.chunk.blocks)[getIndex(x, y, z)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. - const otherBlock = (&neighborMesh.chunk.blocks)[getIndex(otherX, otherY, otherZ)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. - if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { - if(block.transparent()) { - if(block.hasBackFace()) { - try self.transparentMesh.appendNeighbor(constructFaceData(block, neighbor ^ 1, x, y, z, true), neighbor, false); - } - try neighborMesh.transparentMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false); - } else { - try neighborMesh.opaqueMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false); - } - } - if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) { - if(otherBlock.transparent()) { - if(otherBlock.hasBackFace()) { - try neighborMesh.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor, otherX, otherY, otherZ, true), neighbor ^ 1, false); - } - try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false); - } else { - try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false); - } - } - } - } - _ = neighborMesh.needsLightRefresh.swap(false, .AcqRel); - try neighborMesh.finishData(); - if(inRenderThread) { - try neighborMesh.uploadData(); - } else { - neighborMesh.increaseRefCount(); - try renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(neighborMesh); - } - } else { - self.mutex.lock(); - defer self.mutex.unlock(); - if(self.lastNeighborsSameLod[neighbor] != null) { - self.clearNeighbor(neighbor, false); - self.lastNeighborsSameLod[neighbor] = null; - } - } - // lod border: - if(self.pos.voxelSize == 1 << settings.highestLOD) continue; - const neighborMesh = getNeighborMesh(self.pos, 2*self.pos.voxelSize, neighbor) orelse { - self.mutex.lock(); - defer self.mutex.unlock(); - if(self.lastNeighborsHigherLod[neighbor] != null) { - self.clearNeighbor(neighbor, true); - self.lastNeighborsHigherLod[neighbor] = null; - } - continue; - }; - deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); - defer self.mutex.unlock(); - defer neighborMesh.mutex.unlock(); - defer if(!inRenderThread) neighborMesh.decreaseRefCount(); - if(self.lastNeighborsHigherLod[neighbor] == neighborMesh) continue; - self.lastNeighborsHigherLod[neighbor] = neighborMesh; - self.clearNeighbor(neighbor, true); - const x3: i32 = if(neighbor & 1 == 0) chunkMask else 0; - const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunkSize; - const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunkSize; - const offsetZ = @divExact(self.pos.wz, self.pos.voxelSize) & chunkSize; - var x1: i32 = 0; - while(x1 < chunkSize): (x1 += 1) { - var x2: i32 = 0; - while(x2 < chunkSize): (x2 += 1) { - var x: i32 = undefined; - var y: i32 = undefined; - var z: i32 = undefined; - if(Neighbors.relX[neighbor] != 0) { - x = x3; - y = x1; - z = x2; - } else if(Neighbors.relY[neighbor] != 0) { - x = x1; - y = x3; - z = x2; - } else { - x = x2; - y = x1; - z = x3; - } - const otherX = (x+%Neighbors.relX[neighbor]+%offsetX >> 1) & chunkMask; - const otherY = (y+%Neighbors.relY[neighbor]+%offsetY >> 1) & chunkMask; - const otherZ = (z+%Neighbors.relZ[neighbor]+%offsetZ >> 1) & chunkMask; - const block = (&self.chunk.blocks)[getIndex(x, y, z)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. - const otherBlock = (&neighborMesh.chunk.blocks)[getIndex(otherX, otherY, otherZ)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. - if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) { - if(otherBlock.transparent()) { - try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true); - } else { - try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true); - } - } - if(block.hasBackFace()) { - if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { - try self.transparentMesh.appendNeighbor(constructFaceData(block, neighbor ^ 1, x, y, z, true), neighbor, true); - } - } - } - } - } - self.mutex.lock(); - defer self.mutex.unlock(); - _ = self.needsLightRefresh.swap(false, .AcqRel); - try self.finishData(); - } - - pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { - if(self.opaqueMesh.vertexCount == 0) return; - c.glUniform3f( - uniforms.modelPosition, - @floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]), - @floatCast(@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1]), - @floatCast(@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2]) - ); - c.glUniform1i(uniforms.visibilityMask, self.visibilityMask); - c.glUniform1i(uniforms.voxelSize, self.pos.voxelSize); - quadsDrawn += self.opaqueMesh.vertexCount/6; - c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.opaqueMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.opaqueMesh.bufferAllocation.start*4); - } - - pub fn renderTransparent(self: *ChunkMesh, playerPosition: Vec3d) !void { - if(self.transparentMesh.vertexCount == 0) return; - - var needsUpdate: bool = false; - if(self.transparentMesh.wasChanged) { - self.transparentMesh.wasChanged = false; - self.transparentMesh.mutex.lock(); - defer self.transparentMesh.mutex.unlock(); - var len: usize = self.transparentMesh.coreLen; - var offset: usize = self.transparentMesh.coreLen; - var list: [6][]FaceData = undefined; - for(0..6) |i| { - const neighborLen = self.transparentMesh.sameLodLens[i]; - if(!self.isNeighborLod[i]) { - list[i] = self.transparentMesh.completeList[offset..][0..neighborLen]; - len += neighborLen; - } - offset += neighborLen; - } - for(0..6) |i| { - const neighborLen = self.transparentMesh.higherLodLens[i]; - if(self.isNeighborLod[i]) { - list[i] = self.transparentMesh.completeList[offset..][0..neighborLen]; - len += neighborLen; - } - offset += neighborLen; - } - self.sortingOutputBuffer = try main.globalAllocator.realloc(self.sortingOutputBuffer, len); - self.currentSorting = try main.globalAllocator.realloc(self.currentSorting, len); - for(0..self.transparentMesh.coreLen) |i| { - self.currentSorting[i].face = self.transparentMesh.completeList[i]; - } - offset = self.transparentMesh.coreLen; - for(0..6) |n| { - for(0..list[n].len) |i| { - self.currentSorting[offset + i].face = list[n][i]; - } - offset += list[n].len; - } - - needsUpdate = true; - } - - var relativePos = Vec3d { - @as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0], - @as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1], - @as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2] - }/@as(Vec3d, @splat(@as(f64, @floatFromInt(self.pos.voxelSize)))); - relativePos = @min(relativePos, @as(Vec3d, @splat(0))); - relativePos = @max(relativePos, @as(Vec3d, @splat(-32))); - const updatePos: Vec3i = @intFromFloat(relativePos); - if(@reduce(.Or, updatePos != self.lastTransparentUpdatePos)) { - self.lastTransparentUpdatePos = updatePos; - needsUpdate = true; - } - if(needsUpdate) { - for(self.currentSorting) |*val| { - val.update( - updatePos[0], - updatePos[1], - updatePos[2], - ); - } - - // Sort by back vs front face: - { - var backFaceStart: usize = 0; - var i: usize = 0; - var culledStart: usize = self.currentSorting.len; - while(culledStart > 0) { - if(!self.currentSorting[culledStart-1].shouldBeCulled) { - break; - } - culledStart -= 1; - } - while(i < culledStart): (i += 1) { - if(self.currentSorting[i].shouldBeCulled) { - culledStart -= 1; - std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[culledStart]); - while(culledStart > 0) { - if(!self.currentSorting[culledStart-1].shouldBeCulled) { - break; - } - culledStart -= 1; - } - } - if(!self.currentSorting[i].isBackFace) { - std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[backFaceStart]); - backFaceStart += 1; - } - } - self.culledSortingCount = @intCast(culledStart); - } - - // Sort it using bucket sort: - var buckets: [34*3]u32 = undefined; - @memset(&buckets, 0); - for(self.currentSorting[0..self.culledSortingCount]) |val| { - buckets[34*3 - 1 - val.distance] += 1; - } - var prefixSum: u32 = 0; - for(&buckets) |*val| { - const copy = val.*; - val.* = prefixSum; - prefixSum += copy; - } - // Move it over into a new buffer: - for(0..self.culledSortingCount) |i| { - const bucket = 34*3 - 1 - self.currentSorting[i].distance; - self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face; - buckets[bucket] += 1; - } - - // Upload: - try faceBuffer.uploadData(self.sortingOutputBuffer[0..self.culledSortingCount], &self.transparentMesh.bufferAllocation); - } - - c.glUniform3f( - transparentUniforms.modelPosition, - @floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]), - @floatCast(@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1]), - @floatCast(@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2]) - ); - c.glUniform1i(transparentUniforms.visibilityMask, self.visibilityMask); - c.glUniform1i(transparentUniforms.voxelSize, self.pos.voxelSize); - transparentQuadsDrawn += self.culledSortingCount; - c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.culledSortingCount*6, c.GL_UNSIGNED_INT, null, self.transparentMesh.bufferAllocation.start*4); - } - }; }; \ No newline at end of file diff --git a/src/graphics.zig b/src/graphics.zig index 01c15d2f..6859e86e 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -1851,33 +1851,33 @@ pub fn generateBlockTexture(blockType: u16) !Texture { if(block.transparent()) { c.glBlendEquation(c.GL_FUNC_ADD); c.glBlendFunc(c.GL_ONE, c.GL_SRC1_COLOR); - main.chunk.meshing.bindTransparentShaderAndUniforms(projMatrix, .{1, 1, 1}); + main.renderer.chunk_meshing.bindTransparentShaderAndUniforms(projMatrix, .{1, 1, 1}); } else { if(block.mode().model(block).modelIndex == 0) { - main.chunk.meshing.bindShaderAndUniforms(projMatrix, .{1, 1, 1}); + main.renderer.chunk_meshing.bindShaderAndUniforms(projMatrix, .{1, 1, 1}); } else { std.log.err("TODO: Item textures for non-cube models.", .{}); } } - const uniforms = if(block.transparent()) &main.chunk.meshing.transparentUniforms else &main.chunk.meshing.uniforms; + const uniforms = if(block.transparent()) &main.renderer.chunk_meshing.transparentUniforms else &main.renderer.chunk_meshing.uniforms; - var faceData: [6]main.chunk.meshing.FaceData = undefined; + var faceData: [6]main.renderer.chunk_meshing.FaceData = undefined; var faces: u8 = 0; if(block.hasBackFace()) { - faceData[2] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1, 1, 1, true); - faceData[1] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1, 1, true); - faceData[0] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1, true); + faceData[2] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1, 1, 1, true); + faceData[1] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1, 1, true); + faceData[0] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1, true); faces += 3; } - faceData[faces + 0] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1+1, 1, 1, false); - faceData[faces + 1] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1+1, 1, false); - faceData[faces + 2] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1+1, false); + faceData[faces + 0] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1+1, 1, 1, false); + faceData[faces + 1] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1+1, 1, false); + faceData[faces + 2] = main.renderer.chunk_meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1+1, false); faces += 3; for(faceData[0..faces]) |*face| { @memset(&face.light, ~@as(u32, 0)); } var allocation: SubAllocation = .{.start = 0, .len = 0}; - try main.chunk.meshing.faceBuffer.uploadData(faceData[0..faces], &allocation); + try main.renderer.chunk_meshing.faceBuffer.uploadData(faceData[0..faces], &allocation); c.glUniform3f(uniforms.modelPosition, -65.5 - 1.5, -92.631 - 1.5, -65.5 - 1.5); c.glUniform1i(uniforms.visibilityMask, 0xff); @@ -1907,7 +1907,7 @@ pub fn generateBlockTexture(blockType: u16) !Texture { c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); - try main.chunk.meshing.faceBuffer.free(allocation); + try main.renderer.chunk_meshing.faceBuffer.free(allocation); c.glViewport(0, 0, main.Window.width, main.Window.height); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); return texture; diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index 7faa6cc9..748b63fc 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -40,14 +40,14 @@ fn flawedRender() !void { y += 8; try draw.print("Queue size: {}", .{main.threadPool.queueSize()}, 0, y, 8, .left); y += 8; - try draw.print("Mesh Queue size: {}", .{main.renderer.RenderStructure.updatableList.items.len}, 0, y, 8, .left); + try draw.print("Mesh Queue size: {}", .{main.renderer.mesh_storage.updatableList.items.len}, 0, y, 8, .left); y += 8; { - const faceDataSize: usize = @sizeOf(main.chunk.meshing.FaceData); - const size: usize = main.chunk.meshing.faceBuffer.capacity*faceDataSize; - const used: usize = main.chunk.meshing.faceBuffer.used*faceDataSize; + const faceDataSize: usize = @sizeOf(main.renderer.chunk_meshing.FaceData); + const size: usize = main.renderer.chunk_meshing.faceBuffer.capacity*faceDataSize; + const used: usize = main.renderer.chunk_meshing.faceBuffer.used*faceDataSize; var largestFreeBlock: usize = 0; - for(main.chunk.meshing.faceBuffer.freeBlocks.items) |freeBlock| { + for(main.renderer.chunk_meshing.faceBuffer.freeBlocks.items) |freeBlock| { largestFreeBlock = @max(largestFreeBlock, freeBlock.len); } const fragmentation = size - used - largestFreeBlock*faceDataSize; @@ -56,7 +56,7 @@ fn flawedRender() !void { } try draw.print("Biome: {s}", .{main.game.world.?.playerBiome.load(.Monotonic).id}, 0, y, 8, .left); y += 8; - try draw.print("Opaque faces: {}, Transparent faces: {}", .{main.chunk.meshing.quadsDrawn, main.chunk.meshing.transparentQuadsDrawn}, 0, y, 8, .left); + try draw.print("Opaque faces: {}, Transparent faces: {}", .{main.renderer.chunk_meshing.quadsDrawn, main.renderer.chunk_meshing.transparentQuadsDrawn}, 0, y, 8, .left); y += 8; } } diff --git a/src/main.zig b/src/main.zig index abe183d9..7bce0745 100644 --- a/src/main.zig +++ b/src/main.zig @@ -722,17 +722,11 @@ pub fn main() !void { try blocks.meshes.init(); defer blocks.meshes.deinit(); - try chunk.meshing.init(); - defer chunk.meshing.deinit(); - try renderer.init(); defer renderer.deinit(); try network.init(); - try renderer.RenderStructure.init(); - defer renderer.RenderStructure.deinit(); - try entity.ClientEntityManager.init(); defer entity.ClientEntityManager.deinit(); diff --git a/src/network.zig b/src/network.zig index 12f317e4..f3f8be08 100644 --- a/src/network.zig +++ b/src/network.zig @@ -747,7 +747,7 @@ pub const Protocols = struct { block.* = Block.fromInt(std.mem.readInt(u32, data[0..4], .big)); data = data[4..]; } - try renderer.RenderStructure.updateChunkMesh(ch); + try renderer.mesh_storage.updateChunkMesh(ch); } fn sendChunkOverTheNetwork(conn: *Connection, ch: *chunk.Chunk) Allocator.Error!void { var uncompressedData: [@sizeOf(@TypeOf(ch.blocks))]u8 = undefined; // TODO: #15280 @@ -769,7 +769,7 @@ pub const Protocols = struct { const chunkCopy = try main.globalAllocator.create(chunk.Chunk); chunkCopy.init(ch.pos); @memcpy(&chunkCopy.blocks, &ch.blocks); - try renderer.RenderStructure.updateChunkMesh(chunkCopy); + try renderer.mesh_storage.updateChunkMesh(chunkCopy); } pub fn sendChunk(conn: *Connection, ch: *chunk.Chunk) Allocator.Error!void { if(conn.user.?.isLocal) { @@ -854,7 +854,7 @@ pub const Protocols = struct { if(conn.user != null) { // TODO: Handle block update from the client. } else { - try renderer.RenderStructure.updateBlock(x, y, z, newBlock); + try renderer.mesh_storage.updateBlock(x, y, z, newBlock); } } pub fn send(conn: *Connection, x: i32, y: i32, z: i32, newBlock: Block) !void { @@ -1280,7 +1280,7 @@ pub const Protocols = struct { val.* = std.mem.readInt(i16, data[0..2], .big); data = data[2..]; } - try renderer.RenderStructure.updateLightMap(map); + try renderer.mesh_storage.updateLightMap(map); } pub fn sendLightMap(conn: *Connection, map: *main.server.terrain.LightMap.LightMapFragment) Allocator.Error!void { var uncompressedData: [@sizeOf(@TypeOf(map.startHeight))]u8 = undefined; // TODO: #15280 diff --git a/src/renderer.zig b/src/renderer.zig index 07b6bdb6..0b36e002 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -16,17 +16,17 @@ const Window = main.Window; const models = @import("models.zig"); const network = @import("network.zig"); const settings = @import("settings.zig"); -const utils = @import("utils.zig"); const vec = @import("vec.zig"); const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring; -const LightMap = main.server.terrain.LightMap; -const Vec2f = vec.Vec2f; const Vec3i = vec.Vec3i; const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; const Vec4f = vec.Vec4f; const Mat4f = vec.Mat4f; +pub const chunk_meshing = @import("renderer/chunk_meshing.zig"); +pub const mesh_storage = @import("renderer/mesh_storage.zig"); + /// The number of milliseconds after which no more chunk meshes are created. This allows the game to run smoother on movement. const maximumMeshTime = 12; pub const zNear = 0.1; @@ -64,6 +64,8 @@ pub fn init() !void { try Bloom.init(); try MeshSelection.init(); try MenuBackGround.init(); + try chunk_meshing.init(); + try mesh_storage.init(); reflectionCubeMap = graphics.CubeMapTexture.init(); reflectionCubeMap.generate(reflectionCubeMapSize, reflectionCubeMapSize); initReflectionCubeMap(); @@ -76,6 +78,8 @@ pub fn deinit() void { Bloom.deinit(); MeshSelection.deinit(); MenuBackGround.deinit(); + chunk_meshing.deinit(); + mesh_storage.deinit(); reflectionCubeMap.deinit(); } @@ -150,7 +154,7 @@ pub fn render(playerPosition: Vec3d) !void { game.fog.color = skyColor; try renderWorld(world, ambient, skyColor, playerPosition); - try RenderStructure.updateMeshes(startTime + maximumMeshTime); + try mesh_storage.updateMeshes(startTime + maximumMeshTime); } else { // TODO: // clearColor.y = clearColor.z = 0.7f; @@ -180,7 +184,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // Update the uniforms. The uniforms are needed to render the replacement meshes. - chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight); + chunk_meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight); reflectionCubeMap.bindTo(2); c.glActiveTexture(c.GL_TEXTURE0); @@ -191,9 +195,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // SimpleList visibleChunks = new SimpleList(new NormalChunkMesh[64]); // SimpleList visibleReduced = new SimpleList(new ReducedChunkMesh[64]); - chunk.meshing.quadsDrawn = 0; - chunk.meshing.transparentQuadsDrawn = 0; - const meshes = try RenderStructure.updateAndGetRenderChunks(world.conn, playerPos, settings.renderDistance); + chunk_meshing.quadsDrawn = 0; + chunk_meshing.transparentQuadsDrawn = 0; + const meshes = try mesh_storage.updateAndGetRenderChunks(world.conn, playerPos, settings.renderDistance); // for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) { // if (mesh instanceof NormalChunkMesh) { @@ -208,8 +212,8 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo MeshSelection.select(playerPos, game.camera.direction); MeshSelection.render(game.projectionMatrix, game.camera.viewMatrix, playerPos); - try chunk.meshing.beginRender(); - chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight); + try chunk_meshing.beginRender(); + chunk_meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight); for(meshes) |mesh| { mesh.render(playerPos); @@ -232,7 +236,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo gpu_performance_measuring.startQuery(.transparent_rendering); c.glTextureBarrier(); - chunk.meshing.bindTransparentShaderAndUniforms(game.projectionMatrix, ambientLight); + chunk_meshing.bindTransparentShaderAndUniforms(game.projectionMatrix, ambientLight); c.glBlendEquation(c.GL_FUNC_ADD); c.glBlendFunc(c.GL_ONE, c.GL_SRC1_COLOR); @@ -249,7 +253,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo c.glDepthMask(c.GL_TRUE); c.glDepthFunc(c.GL_LESS); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); - chunk.meshing.endRender(); + chunk_meshing.endRender(); gpu_performance_measuring.stopQuery(); // NormalChunkMesh.bindTransparentShader(ambientLight, directionalLight.getDirection(), time); @@ -263,7 +267,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // Meshes.emissionTextureArray.bind(); // } - const playerBlock = RenderStructure.getBlockFromAnyLodFromRenderThread(@intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2]))); + const playerBlock = mesh_storage.getBlockFromAnyLodFromRenderThread(@intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2]))); if(settings.bloom) { Bloom.render(lastWidth, lastHeight, playerBlock); @@ -718,7 +722,7 @@ pub const MeshSelection = struct { selectedBlockPos = null; while(total_tMax < closestDistance) { - const block = RenderStructure.getBlockFromRenderThread(voxelPos[0], voxelPos[1], voxelPos[2]) orelse break; + const block = mesh_storage.getBlockFromRenderThread(voxelPos[0], voxelPos[1], voxelPos[2]) orelse break; if(block.typ != 0) { // Check the true bounding box (using this algorithm here: https://tavianator.com/2011/ray_box.html): const model = blocks.meshes.model(block); @@ -765,7 +769,7 @@ pub const MeshSelection = struct { pub fn placeBlock(inventoryStack: *main.items.ItemStack) !void { if(selectedBlockPos) |selectedPos| { - var block = RenderStructure.getBlockFromRenderThread(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; + var block = mesh_storage.getBlockFromRenderThread(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return; if(inventoryStack.item) |item| { switch(item) { .baseItem => |baseItem| { @@ -777,7 +781,7 @@ pub const MeshSelection = struct { const relPos = lastPos - @as(Vec3d, @floatFromInt(selectedPos)); if(rotationMode.generateData(main.game.world.?, selectedPos, relPos, lastDir, neighborDir, &block, false)) { // TODO: world.updateBlock(bi.x, bi.y, bi.z, block.data); (→ Sending it over the network) - try RenderStructure.updateBlock(selectedPos[0], selectedPos[1], selectedPos[2], block); + try mesh_storage.updateBlock(selectedPos[0], selectedPos[1], selectedPos[2], block); _ = inventoryStack.add(@as(i32, -1)); return; } @@ -786,11 +790,11 @@ pub const MeshSelection = struct { const neighborPos = posBeforeBlock; neighborDir = selectedPos - posBeforeBlock; const relPos = lastPos - @as(Vec3d, @floatFromInt(neighborPos)); - block = RenderStructure.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; + block = mesh_storage.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; if(block.typ == itemBlock) { if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, false)) { // TODO: world.updateBlock(bi.x, bi.y, bi.z, block.data); (→ Sending it over the network) - try RenderStructure.updateBlock(neighborPos[0], neighborPos[1], neighborPos[2], block); + try mesh_storage.updateBlock(neighborPos[0], neighborPos[1], neighborPos[2], block); _ = inventoryStack.add(@as(i32, -1)); return; } @@ -810,7 +814,7 @@ pub const MeshSelection = struct { block.data = 0; if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, &block, true)) { // TODO: world.updateBlock(bi.x, bi.y, bi.z, block.data); (→ Sending it over the network) - try RenderStructure.updateBlock(neighborPos[0], neighborPos[1], neighborPos[2], block); + try mesh_storage.updateBlock(neighborPos[0], neighborPos[1], neighborPos[2], block); _ = inventoryStack.add(@as(i32, -1)); return; } @@ -827,7 +831,7 @@ pub const MeshSelection = struct { pub fn breakBlock() !void { if(selectedBlockPos) |selectedPos| { - try RenderStructure.updateBlock(selectedPos[0], selectedPos[1], selectedPos[2], .{.typ = 0, .data = 0}); + try mesh_storage.updateBlock(selectedPos[0], selectedPos[1], selectedPos[2], .{.typ = 0, .data = 0}); } } @@ -855,7 +859,7 @@ pub const MeshSelection = struct { c.glEnable(c.GL_POLYGON_OFFSET_LINE); defer c.glDisable(c.GL_POLYGON_OFFSET_LINE); c.glPolygonOffset(-2, 0); - const block = RenderStructure.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return; + const block = mesh_storage.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return; const model = blocks.meshes.model(block); const modelData = &models.models.items[model.modelIndex]; const transformedMin = model.permutation.transform(modelData.min - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8)); @@ -866,1040 +870,3 @@ pub const MeshSelection = struct { } } }; - -pub const RenderStructure = struct { - const ChunkMeshNode = struct { - mesh: Atomic(?*chunk.meshing.ChunkMesh), - lod: u3, - min: Vec2f, - max: Vec2f, - active: bool, - rendered: bool, - }; - const storageSize = 32; - const storageMask = storageSize - 1; - var storageLists: [settings.highestLOD + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined; - var mapStorageLists: [settings.highestLOD + 1]*[storageSize*storageSize]Atomic(?*LightMap.LightMapFragment) = undefined; - var meshList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); - var priorityMeshUpdateList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); - pub var updatableList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); - var mapUpdatableList = std.ArrayList(*LightMap.LightMapFragment).init(main.globalAllocator); - var clearList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator); - var lastPx: i32 = 0; - var lastPy: i32 = 0; - var lastPz: i32 = 0; - var lastRD: i32 = 0; - var mutex = std.Thread.Mutex{}; - var blockUpdateMutex = std.Thread.Mutex{}; - const BlockUpdate = struct { - x: i32, - y: i32, - z: i32, - newBlock: blocks.Block, - }; - var blockUpdateList: std.ArrayList(BlockUpdate) = undefined; - - pub fn init() !void { - lastRD = 0; - blockUpdateList = std.ArrayList(BlockUpdate).init(main.globalAllocator); - for(&storageLists) |*storageList| { - storageList.* = try main.globalAllocator.create([storageSize*storageSize*storageSize]ChunkMeshNode); - for(storageList.*) |*val| { - val.mesh = Atomic(?*chunk.meshing.ChunkMesh).init(null); - val.rendered = false; - } - } - for(&mapStorageLists) |*mapStorageList| { - mapStorageList.* = try main.globalAllocator.create([storageSize*storageSize]Atomic(?*LightMap.LightMapFragment)); - @memset(mapStorageList.*, Atomic(?*LightMap.LightMapFragment).init(null)); - } - } - - pub fn deinit() void { - const olderPx = lastPx; - const olderPy = lastPy; - const olderPz = lastPz; - const olderRD = lastRD; - lastPx = 0; - lastPy = 0; - lastPz = 0; - lastRD = 0; - freeOldMeshes(olderPx, olderPy, olderPz, olderRD) catch |err| { - std.log.err("Error while freeing remaining meshes: {s}", .{@errorName(err)}); - }; - for(storageLists) |storageList| { - main.globalAllocator.destroy(storageList); - } - for(mapStorageLists) |mapStorageList| { - main.globalAllocator.destroy(mapStorageList); - } - for(updatableList.items) |mesh| { - mesh.decreaseRefCount(); - } - updatableList.deinit(); - for(mapUpdatableList.items) |map| { - map.decreaseRefCount(); - } - mapUpdatableList.deinit(); - for(priorityMeshUpdateList.items) |mesh| { - mesh.decreaseRefCount(); - } - priorityMeshUpdateList.deinit(); - blockUpdateList.deinit(); - meshList.deinit(); - for(clearList.items) |mesh| { - mesh.deinit(); - main.globalAllocator.destroy(mesh); - } - clearList.deinit(); - } - - fn getNodeFromRenderThread(pos: chunk.ChunkPosition) *ChunkMeshNode { - const lod = std.math.log2_int(u31, pos.voxelSize); - var xIndex = pos.wx >> lod+chunk.chunkShift; - var yIndex = pos.wy >> lod+chunk.chunkShift; - var zIndex = pos.wz >> lod+chunk.chunkShift; - xIndex &= storageMask; - yIndex &= storageMask; - zIndex &= storageMask; - const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; - return &storageLists[lod][@intCast(index)]; - } - - fn getMapPieceLocation(x: i32, z: i32, voxelSize: u31) *Atomic(?*LightMap.LightMapFragment) { - const lod = std.math.log2_int(u31, voxelSize); - var xIndex = x >> lod+LightMap.LightMapFragment.mapShift; - var zIndex = z >> lod+LightMap.LightMapFragment.mapShift; - xIndex &= storageMask; - zIndex &= storageMask; - const index = xIndex*storageSize + zIndex; - return &(&mapStorageLists)[lod][@intCast(index)]; - } - - pub fn getLightMapPieceAndIncreaseRefCount(x: i32, z: i32, voxelSize: u31) ?*LightMap.LightMapFragment { - const result: *LightMap.LightMapFragment = getMapPieceLocation(x, z, voxelSize).load(.Acquire) orelse return null; - var refCount: u16 = 1; - while(result.refCount.cmpxchgWeak(refCount, refCount+1, .Monotonic, .Monotonic)) |otherVal| { - if(otherVal == 0) return null; - refCount = otherVal; - } - return result; - } - - fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block { - const node = RenderStructure.getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=1}); - const mesh = node.mesh.load(.Acquire) orelse return null; - const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); - return block; - } - - fn getBlockFromAnyLodFromRenderThread(x: i32, y: i32, z: i32) blocks.Block { - var lod: u5 = 0; - while(lod < settings.highestLOD) : (lod += 1) { - const node = RenderStructure.getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=@as(u31, 1) << lod}); - const mesh = node.mesh.load(.Acquire) orelse continue; - const block = mesh.chunk.getBlock(x & chunk.chunkMask<= maxX) return false; - var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx); - deltaX = @max(0, deltaX - size/2); - - const maxYRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX); - const minY = lastPy-%maxYRenderDistance & invMask; - const maxY = lastPy+%maxYRenderDistance+%size & invMask; - if(pos.wy < minY) return false; - if(pos.wy >= maxY) return false; - var deltaY: i64 = @abs(pos.wy +% size/2 -% lastPy); - deltaY = @max(0, deltaY - size/2); - - const maxZRenderDistance: i32 = reduceRenderDistance(maxYRenderDistance, deltaY); - if(maxZRenderDistance == 0) return false; - const minZ = lastPz-%maxZRenderDistance & invMask; - const maxZ = lastPz+%maxZRenderDistance+%size & invMask; - if(pos.wz < minZ) return false; - if(pos.wz >= maxZ) return false; - return true; - } - - fn isMapInRenderDistance(pos: LightMap.MapFragmentPosition) bool { - const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize; - const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize)*pos.voxelSize; - const mask: i32 = size - 1; - const invMask: i32 = ~mask; - - const minX = lastPx-%maxRenderDistance & invMask; - const maxX = lastPx+%maxRenderDistance+%size & invMask; - if(pos.wx < minX) return false; - if(pos.wx >= maxX) return false; - var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx); - deltaX = @max(0, deltaX - size/2); - - const maxZRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX); - if(maxZRenderDistance == 0) return false; - const minZ = lastPz-%maxZRenderDistance & invMask; - const maxZ = lastPz+%maxZRenderDistance+%size & invMask; - if(pos.wz < minZ) return false; - if(pos.wz >= maxZ) return false; - return true; - } - - fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32) !void { - for(0..storageLists.len) |_lod| { - const lod: u5 = @intCast(_lod); - const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; - const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; - const size: u31 = chunk.chunkSize << lod; - const mask: i32 = size - 1; - const invMask: i32 = ~mask; - - std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); - - const minX = olderPx-%maxRenderDistanceOld & invMask; - const maxX = olderPx+%maxRenderDistanceOld+%size & invMask; - var x = minX; - while(x != maxX): (x +%= size) { - const xIndex = @divExact(x, size) & storageMask; - var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); - deltaXNew = @max(0, deltaXNew - size/2); - var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); - deltaXOld = @max(0, deltaXOld - size/2); - const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); - const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); - - const minY = olderPy-%maxYRenderDistanceOld & invMask; - const maxY = olderPy+%maxYRenderDistanceOld+%size & invMask; - var y = minY; - while(y != maxY): (y +%= size) { - const yIndex = @divExact(y, size) & storageMask; - var deltaYOld: i64 = @abs(y +% size/2 -% olderPy); - deltaYOld = @max(0, deltaYOld - size/2); - var deltaYNew: i64 = @abs(y +% size/2 -% lastPy); - deltaYNew = @max(0, deltaYNew - size/2); - var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld); - if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; - var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew); - if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; - - const minZOld = olderPz-%maxZRenderDistanceOld & invMask; - const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; - const minZNew = lastPz-%maxZRenderDistanceNew & invMask; - const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; - - var zValues: [storageSize]i32 = undefined; - var zValuesLen: usize = 0; - if(minZNew -% minZOld > 0) { - var z = minZOld; - while(z != minZNew and z != maxZOld): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - if(maxZOld -% maxZNew > 0) { - var z = minZOld +% @max(0, maxZNew -% minZOld); - while(z != maxZOld): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - - for(zValues[0..zValuesLen]) |z| { - const zIndex = @divExact(z, size) & storageMask; - const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; - - const node = &storageLists[_lod][@intCast(index)]; - if(node.mesh.load(.Acquire)) |mesh| { - mesh.decreaseRefCount(); - node.mesh.store(null, .Release); - } - } - } - } - } - for(0..mapStorageLists.len) |_lod| { - const lod: u5 = @intCast(_lod); - const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; - const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; - const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod; - const mask: i32 = size - 1; - const invMask: i32 = ~mask; - - std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); - - const minX = olderPx-%maxRenderDistanceOld & invMask; - const maxX = olderPx+%maxRenderDistanceOld+%size & invMask; - var x = minX; - while(x != maxX): (x +%= size) { - const xIndex = @divExact(x, size) & storageMask; - var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); - deltaXNew = @max(0, deltaXNew - size/2); - var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); - deltaXOld = @max(0, deltaXOld - size/2); - var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); - if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; - var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); - if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; - - const minZOld = olderPz-%maxZRenderDistanceOld & invMask; - const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; - const minZNew = lastPz-%maxZRenderDistanceNew & invMask; - const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; - - var zValues: [storageSize]i32 = undefined; - var zValuesLen: usize = 0; - if(minZNew -% minZOld > 0) { - var z = minZOld; - while(z != minZNew and z != maxZOld): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - if(maxZOld -% maxZNew > 0) { - var z = minZOld +% @max(0, maxZNew -% minZOld); - while(z != maxZOld): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - - for(zValues[0..zValuesLen]) |z| { - const zIndex = @divExact(z, size) & storageMask; - const index = xIndex*storageSize + zIndex; - - const mapAtomic = &mapStorageLists[_lod][@intCast(index)]; - if(mapAtomic.load(.Acquire)) |map| { - mapAtomic.store(null, .Release); - map.decreaseRefCount(); - } - } - } - } - } - - fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition), mapRequests: *std.ArrayList(LightMap.MapFragmentPosition)) !void { - for(0..storageLists.len) |_lod| { - const lod: u5 = @intCast(_lod); - const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; - const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; - const size: u31 = chunk.chunkSize << lod; - const mask: i32 = size - 1; - const invMask: i32 = ~mask; - - std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); - - const minX = lastPx-%maxRenderDistanceNew & invMask; - const maxX = lastPx+%maxRenderDistanceNew+%size & invMask; - var x = minX; - while(x != maxX): (x +%= size) { - const xIndex = @divExact(x, size) & storageMask; - var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); - deltaXNew = @max(0, deltaXNew - size/2); - var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); - deltaXOld = @max(0, deltaXOld - size/2); - const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); - const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); - - const minY = lastPy-%maxYRenderDistanceNew & invMask; - const maxY = lastPy+%maxYRenderDistanceNew+%size & invMask; - var y = minY; - while(y != maxY): (y +%= size) { - const yIndex = @divExact(y, size) & storageMask; - var deltaYOld: i64 = @abs(y +% size/2 -% olderPy); - deltaYOld = @max(0, deltaYOld - size/2); - var deltaYNew: i64 = @abs(y +% size/2 -% lastPy); - deltaYNew = @max(0, deltaYNew - size/2); - var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew); - if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; - var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld); - if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; - - const minZOld = olderPz-%maxZRenderDistanceOld & invMask; - const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; - const minZNew = lastPz-%maxZRenderDistanceNew & invMask; - const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; - - var zValues: [storageSize]i32 = undefined; - var zValuesLen: usize = 0; - if(minZOld -% minZNew > 0) { - var z = minZNew; - while(z != minZOld and z != maxZNew): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - if(maxZNew -% maxZOld > 0) { - var z = minZNew +% @max(0, maxZOld -% minZNew); - while(z != maxZNew): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - - for(zValues[0..zValuesLen]) |z| { - const zIndex = @divExact(z, size) & storageMask; - const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; - const pos = chunk.ChunkPosition{.wx=x, .wy=y, .wz=z, .voxelSize=@as(u31, 1)< 0) { - var z = minZNew; - while(z != minZOld and z != maxZNew): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - if(maxZNew -% maxZOld > 0) { - var z = minZNew +% @max(0, maxZOld -% minZNew); - while(z != maxZNew): (z +%= size) { - zValues[zValuesLen] = z; - zValuesLen += 1; - } - } - - for(zValues[0..zValuesLen]) |z| { - const zIndex = @divExact(z, size) & storageMask; - const index = xIndex*storageSize + zIndex; - const pos = LightMap.MapFragmentPosition{.wx=x, .wz=z, .voxelSize=@as(u31, 1)< b.distance) return .gt; - return .eq; - } - }; - - // TODO: Is there a way to combine this with minecraft's approach? - var searchList = std.PriorityQueue(OcclusionData, void, OcclusionData.compare).init(main.globalAllocator, {}); - defer searchList.deinit(); - { - var firstPos = chunk.ChunkPosition{ - .wx = @intFromFloat(@floor(playerPos[0])), - .wy = @intFromFloat(@floor(playerPos[1])), - .wz = @intFromFloat(@floor(playerPos[2])), - .voxelSize = 1, - }; - firstPos.wx &= ~@as(i32, chunk.chunkMask); - firstPos.wy &= ~@as(i32, chunk.chunkMask); - firstPos.wz &= ~@as(i32, chunk.chunkMask); - var lod: u3 = 0; - while(lod <= settings.highestLOD) : (lod += 1) { - const node = getNodeFromRenderThread(firstPos); - if(node.mesh.load(.Acquire) != null and node.mesh.load(.Acquire).?.finishedMeshing) { - node.lod = lod; - node.min = @splat(-1); - node.max = @splat(1); - node.active = true; - node.rendered = true; - try searchList.add(.{ - .node = node, - .distance = 0, - }); - break; - } - firstPos.wx &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); - firstPos.wy &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); - firstPos.wz &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); - firstPos.voxelSize *= 2; - } - } - var nodeList = std.ArrayList(*ChunkMeshNode).init(main.globalAllocator); - defer nodeList.deinit(); - const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix); - while(searchList.removeOrNull()) |data| { - try nodeList.append(data.node); - data.node.active = false; - const mesh = data.node.mesh.load(.Acquire).?; - std.debug.assert(mesh.finishedMeshing); - mesh.visibilityMask = 0xff; - const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz})) - playerPos; - const relPosFloat: Vec3f = @floatCast(relPos); - var isNeighborLod: [6]bool = .{false} ** 6; - for(chunk.Neighbors.iterable) |neighbor| continueNeighborLoop: { - const component = chunk.Neighbors.extractDirectionComponent(neighbor, relPos); - if(chunk.Neighbors.isPositive[neighbor] and component + @as(f64, @floatFromInt(chunk.chunkSize*mesh.pos.voxelSize)) <= 0) continue; - if(!chunk.Neighbors.isPositive[neighbor] and component >= 0) continue; - if(@reduce(.Or, @min(mesh.chunkBorders[neighbor].min, mesh.chunkBorders[neighbor].max) != mesh.chunkBorders[neighbor].min)) continue; // There was not a single block in the chunk. TODO: Find a better solution. - const minVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor].min*@as(Vec3i, @splat(mesh.pos.voxelSize))); - const maxVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor].max*@as(Vec3i, @splat(mesh.pos.voxelSize))); - var xyMin: Vec2f = .{10, 10}; - var xyMax: Vec2f = .{-10, -10}; - var numberOfNegatives: u8 = 0; - var corners: [5]Vec4f = undefined; - var curCorner: usize = 0; - for(0..2) |a| { - for(0..2) |b| { - - var cornerVector: Vec3f = undefined; - switch(chunk.Neighbors.vectorComponent[neighbor]) { - .x => { - cornerVector = @select(f32, @Vector(3, bool){true, a == 0, b == 0}, minVec, maxVec); - }, - .y => { - cornerVector = @select(f32, @Vector(3, bool){a == 0, true, b == 0}, minVec, maxVec); - }, - .z => { - cornerVector = @select(f32, @Vector(3, bool){a == 0, b == 0, true}, minVec, maxVec); - }, - } - corners[curCorner] = projRotMat.mulVec(vec.combine(relPosFloat + cornerVector, 1)); - if(corners[curCorner][3] < 0) { - numberOfNegatives += 1; - } - curCorner += 1; - } - } - switch(numberOfNegatives) { // Oh, so complicated. But this should only trigger very close to the player. - 4 => continue, - 0 => {}, - 1 => { - // Needs to duplicate the problematic corner and move it onto the projected plane. - var problematicOne: usize = 0; - for(0..curCorner) |i| { - if(corners[i][3] < 0) { - problematicOne = i; - break; - } - } - const problematicVector = corners[problematicOne]; - // The two neighbors of the quad: - const neighborA = corners[problematicOne ^ 1]; - const neighborB = corners[problematicOne ^ 2]; - // Move the problematic point towards the neighbor: - const one: Vec4f = @splat(1); - const weightA: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborA[3])); - var towardsA = neighborA*weightA + problematicVector*(one - weightA); - towardsA[3] = 0; // Prevent inaccuracies - const weightB: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborB[3])); - var towardsB = neighborB*weightB + problematicVector*(one - weightB); - towardsB[3] = 0; // Prevent inaccuracies - corners[problematicOne] = towardsA; - corners[curCorner] = towardsB; - curCorner += 1; - }, - 2 => { - // Needs to move the two problematic corners onto the projected plane. - var problematicOne: usize = undefined; - for(0..curCorner) |i| { - if(corners[i][3] < 0) { - problematicOne = i; - break; - } - } - const problematicVectorOne = corners[problematicOne]; - var problematicTwo: usize = undefined; - for(problematicOne+1..curCorner) |i| { - if(corners[i][3] < 0) { - problematicTwo = i; - break; - } - } - const problematicVectorTwo = corners[problematicTwo]; - - const commonDirection = problematicOne ^ problematicTwo; - const projectionDirection = commonDirection ^ 0b11; - // The respective neighbors: - const neighborOne = corners[problematicOne ^ projectionDirection]; - const neighborTwo = corners[problematicTwo ^ projectionDirection]; - // Move the problematic points towards the neighbor: - const one: Vec4f = @splat(1); - const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborOne[3])); - var towardsOne = neighborOne*weightOne + problematicVectorOne*(one - weightOne); - towardsOne[3] = 0; // Prevent inaccuracies - corners[problematicOne] = towardsOne; - - const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborTwo[3])); - var towardsTwo = neighborTwo*weightTwo + problematicVectorTwo*(one - weightTwo); - towardsTwo[3] = 0; // Prevent inaccuracies - corners[problematicTwo] = towardsTwo; - }, - 3 => { - // Throw away the far problematic vector, move the other two onto the projection plane. - var neighborIndex: usize = undefined; - for(0..curCorner) |i| { - if(corners[i][3] >= 0) { - neighborIndex = i; - break; - } - } - const neighborVector = corners[neighborIndex]; - const problematicVectorOne = corners[neighborIndex ^ 1]; - const problematicVectorTwo = corners[neighborIndex ^ 2]; - // Move the problematic points towards the neighbor: - const one: Vec4f = @splat(1); - const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborVector[3])); - var towardsOne = neighborVector*weightOne + problematicVectorOne*(one - weightOne); - towardsOne[3] = 0; // Prevent inaccuracies - - const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborVector[3])); - var towardsTwo = neighborVector*weightTwo + problematicVectorTwo*(one - weightTwo); - towardsTwo[3] = 0; // Prevent inaccuracies - - corners[0] = neighborVector; - corners[1] = towardsOne; - corners[2] = towardsTwo; - curCorner = 3; - }, - else => unreachable, - } - - for(0..curCorner) |i| { - const projected = corners[i]; - const xy = vec.xy(projected)/@as(Vec2f, @splat(@max(0, projected[3]))); - xyMin = @min(xyMin, xy); - xyMax = @max(xyMax, xy); - } - const min = @max(xyMin, data.node.min); - const max = @min(xyMax, data.node.max); - if(@reduce(.Or, min >= max)) continue; // Nothing to render. - var neighborPos = chunk.ChunkPosition{ - .wx = mesh.pos.wx + chunk.Neighbors.relX[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, - .wy = mesh.pos.wy + chunk.Neighbors.relY[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, - .wz = mesh.pos.wz + chunk.Neighbors.relZ[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, - .voxelSize = mesh.pos.voxelSize, - }; - var lod: u3 = data.node.lod; - while(lod <= settings.highestLOD) : (lod += 1) { - defer { - neighborPos.wx &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.wy &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.wz &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.voxelSize *= 2; - } - const node = getNodeFromRenderThread(neighborPos); - if(node.mesh.load(.Acquire)) |neighborMesh| { - if(!neighborMesh.finishedMeshing) continue; - // Ensure that there are no high-to-low lod transitions, which would produce cracks. - if(lod == data.node.lod and lod != settings.highestLOD and !node.rendered) { - var isValid: bool = true; - const relPos2: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{neighborPos.wx, neighborPos.wy, neighborPos.wz})) - playerPos; - for(chunk.Neighbors.iterable) |neighbor2| { - const component2 = chunk.Neighbors.extractDirectionComponent(neighbor2, relPos2); - if(chunk.Neighbors.isPositive[neighbor2] and component2 + @as(f64, @floatFromInt(chunk.chunkSize*neighborMesh.pos.voxelSize)) >= 0) continue; - if(!chunk.Neighbors.isPositive[neighbor2] and component2 <= 0) continue; - { // Check the chunk of same lod: - const neighborPos2 = chunk.ChunkPosition{ - .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .voxelSize = neighborPos.voxelSize, - }; - const node2 = getNodeFromRenderThread(neighborPos2); - if(node2.rendered) { - continue; - } - } - { // Check the chunk of higher lod - const neighborPos2 = chunk.ChunkPosition{ - .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, - .voxelSize = neighborPos.voxelSize << 1, - }; - const node2 = getNodeFromRenderThread(neighborPos2); - if(node2.rendered) { - isValid = false; - break; - } - } - } - if(!isValid) { - isNeighborLod[neighbor] = true; - continue; - } - } - if(lod != data.node.lod) { - isNeighborLod[neighbor] = true; - } - if(node.active) { - node.min = @min(node.min, min); - node.max = @max(node.max, max); - } else { - node.lod = lod; - node.min = min; - node.max = max; - node.active = true; - try searchList.add(.{ - .node = node, - .distance = neighborMesh.pos.getMaxDistanceSquared(playerPos), - }); - node.rendered = true; - } - break :continueNeighborLoop; - } - } - } - try mesh.changeLodBorders(isNeighborLod); - } - for(nodeList.items) |node| { - node.rendered = false; - const mesh = node.mesh.load(.Acquire).?; - if(mesh.pos.voxelSize != @as(u31, 1) << settings.highestLOD) { - const parent = getNodeFromRenderThread(.{.wx=mesh.pos.wx, .wy=mesh.pos.wy, .wz=mesh.pos.wz, .voxelSize=mesh.pos.voxelSize << 1}); - if(parent.mesh.load(.Acquire)) |parentMesh| { - const sizeShift = chunk.chunkShift + @ctz(mesh.pos.voxelSize); - const octantIndex: u3 = @intCast((mesh.pos.wx>>sizeShift & 1) | (mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.pos.wz>>sizeShift & 1)<<2); - parentMesh.visibilityMask &= ~(@as(u8, 1) << octantIndex); - } - } - mutex.lock(); - if(mesh.needsMeshUpdate) { - try mesh.uploadData(); - mesh.needsMeshUpdate = false; - } - mutex.unlock(); - // Remove empty meshes. - if(!mesh.isEmpty()) { - try meshList.append(mesh); - } - } - - return meshList.items; - } - - pub fn updateMeshes(targetTime: i64) !void { - { // First of all process all the block updates: - blockUpdateMutex.lock(); - defer blockUpdateMutex.unlock(); - for(blockUpdateList.items) |blockUpdate| { - const pos = chunk.ChunkPosition{.wx=blockUpdate.x, .wy=blockUpdate.y, .wz=blockUpdate.z, .voxelSize=1}; - const node = getNodeFromRenderThread(pos); - if(node.mesh.load(.Acquire)) |mesh| { - try mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock); - } // TODO: It seems like we simply ignore the block update if we don't have the mesh yet. - } - blockUpdateList.clearRetainingCapacity(); - } - mutex.lock(); - defer mutex.unlock(); - for(clearList.items) |mesh| { - mesh.deinit(); - main.globalAllocator.destroy(mesh); - } - clearList.clearRetainingCapacity(); - while (priorityMeshUpdateList.items.len != 0) { - const mesh = priorityMeshUpdateList.orderedRemove(0); - if(!mesh.needsMeshUpdate) { - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); - continue; - } - mesh.needsMeshUpdate = false; - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); - if(getNodeFromRenderThread(mesh.pos).mesh.load(.Acquire) != mesh) continue; // This mesh isn't used for rendering anymore. - try mesh.uploadData(); - if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. - } - while(mapUpdatableList.popOrNull()) |map| { - if(!isMapInRenderDistance(map.pos)) { - map.decreaseRefCount(); - } else { - if(getMapPieceLocation(map.pos.wx, map.pos.wz, map.pos.voxelSize).swap(map, .AcqRel)) |old| { - old.decreaseRefCount(); - } - } - } - while(updatableList.items.len != 0) { - // TODO: Find a faster solution than going through the entire list every frame. - var closestPriority: f32 = -std.math.floatMax(f32); - var closestIndex: usize = 0; - const playerPos = game.Player.getPosBlocking(); - { - var i: usize = 0; - while(i < updatableList.items.len) { - const mesh = updatableList.items[i]; - if(!isInRenderDistance(mesh.pos)) { - _ = updatableList.swapRemove(i); - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); - continue; - } - const priority = mesh.pos.getPriority(playerPos); - if(priority > closestPriority) { - closestPriority = priority; - closestIndex = i; - } - i += 1; - } - if(updatableList.items.len == 0) break; - } - const mesh = updatableList.swapRemove(closestIndex); - mutex.unlock(); - defer mutex.lock(); - if(isInRenderDistance(mesh.pos)) { - const node = getNodeFromRenderThread(mesh.pos); - mesh.finishedMeshing = true; - try mesh.uploadData(); - if(node.mesh.swap(mesh, .AcqRel)) |oldMesh| { - oldMesh.decreaseRefCount(); - } - } else { - mesh.decreaseRefCount(); - } - if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. - } - } - - 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 addToUpdateListAndDecreaseRefCount(mesh: *chunk.meshing.ChunkMesh) !void { - std.debug.assert(mesh.refCount.load(.Monotonic) != 0); - mutex.lock(); - defer mutex.unlock(); - if(mesh.finishedMeshing) { - try priorityMeshUpdateList.append(mesh); - mesh.needsMeshUpdate = true; - } else { - mutex.unlock(); - defer mutex.lock(); - mesh.decreaseRefCount(); - } - } - - pub fn addMeshToStorage(mesh: *chunk.meshing.ChunkMesh) !void { - mutex.lock(); - defer mutex.unlock(); - if(isInRenderDistance(mesh.pos)) { - const node = getNodeFromRenderThread(mesh.pos); - if(node.mesh.cmpxchgStrong(null, mesh, .AcqRel, .Monotonic) != null) { - return error.AlreadyStored; - } else { - mesh.increaseRefCount(); - } - } - } - - pub const MeshGenerationTask = struct { - mesh: *chunk.Chunk, - - pub const vtable = utils.ThreadPool.VTable{ - .getPriority = @ptrCast(&getPriority), - .isStillNeeded = @ptrCast(&isStillNeeded), - .run = @ptrCast(&run), - .clean = @ptrCast(&clean), - }; - - pub fn schedule(mesh: *chunk.Chunk) !void { - const task = try main.globalAllocator.create(MeshGenerationTask); - task.* = MeshGenerationTask { - .mesh = mesh, - }; - try main.threadPool.addTask(task, &vtable); - } - - pub fn getPriority(self: *MeshGenerationTask) f32 { - return self.mesh.pos.getPriority(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time. - } - - pub fn isStillNeeded(self: *MeshGenerationTask) bool { - const distanceSqr = self.mesh.pos.getMinDistanceSquared(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time. - var maxRenderDistance = settings.renderDistance*chunk.chunkSize*self.mesh.pos.voxelSize; - maxRenderDistance += 2*self.mesh.pos.voxelSize*chunk.chunkSize; - return distanceSqr < @as(f64, @floatFromInt(maxRenderDistance*maxRenderDistance)); - } - - pub fn run(self: *MeshGenerationTask) Allocator.Error!void { - const pos = self.mesh.pos; - const mesh = try main.globalAllocator.create(chunk.meshing.ChunkMesh); - try mesh.init(pos, self.mesh); - mesh.regenerateMainMesh() catch |err| { - switch(err) { - error.AlreadyStored => { - mesh.decreaseRefCount(); - main.globalAllocator.destroy(self); - return; - }, - else => |_err| { - return _err; - } - } - }; - mutex.lock(); - defer mutex.unlock(); - updatableList.append(mesh) catch |err| { - std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)}); - if(@errorReturnTrace()) |trace| { - std.log.err("Trace: {}", .{trace}); - } - main.globalAllocator.destroy(self.mesh); - }; - main.globalAllocator.destroy(self); - } - - pub fn clean(self: *MeshGenerationTask) void { - main.globalAllocator.destroy(self.mesh); - main.globalAllocator.destroy(self); - } - }; - - pub fn updateBlock(x: i32, y: i32, z: i32, newBlock: blocks.Block) !void { - blockUpdateMutex.lock(); - try blockUpdateList.append(BlockUpdate{.x=x, .y=y, .z=z, .newBlock=newBlock}); - defer blockUpdateMutex.unlock(); - } - - pub fn updateChunkMesh(mesh: *chunk.Chunk) !void { - try MeshGenerationTask.schedule(mesh); - } - - pub fn updateLightMap(map: *LightMap.LightMapFragment) !void { - mutex.lock(); - defer mutex.unlock(); - try mapUpdatableList.append(map); - } -}; diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig new file mode 100644 index 00000000..d370c89b --- /dev/null +++ b/src/renderer/chunk_meshing.zig @@ -0,0 +1,1118 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const main = @import("root"); +const blocks = main.blocks; +const Block = blocks.Block; +const chunk = main.chunk; +const game = main.game; +const models = main.models; +const renderer = main.renderer; +const graphics = main.graphics; +const c = graphics.c; +const Shader = graphics.Shader; +const SSBO = graphics.SSBO; +const lighting = @import("lighting.zig"); +const settings = main.settings; +const vec = main.vec; +const Vec2f = vec.Vec2f; +const Vec3i = vec.Vec3i; +const Vec3f = vec.Vec3f; +const Vec3d = vec.Vec3d; +const Mat4f = vec.Mat4f; + +const mesh_storage = @import("mesh_storage.zig"); + +var shader: Shader = undefined; +var transparentShader: Shader = undefined; +const UniformStruct = struct { + projectionMatrix: c_int, + viewMatrix: c_int, + modelPosition: c_int, + screenSize: c_int, + ambientLight: c_int, + @"fog.color": c_int, + @"fog.density": c_int, + texture_sampler: c_int, + emissionSampler: c_int, + reflectionMap: c_int, + reflectionMapSize: c_int, + visibilityMask: c_int, + voxelSize: c_int, + zNear: c_int, + zFar: c_int, +}; +pub var uniforms: UniformStruct = undefined; +pub var transparentUniforms: UniformStruct = undefined; +var vao: c_uint = undefined; +var vbo: c_uint = undefined; +var faces: std.ArrayList(u32) = undefined; +pub var faceBuffer: graphics.LargeBuffer(FaceData) = undefined; +pub var quadsDrawn: usize = 0; +pub var transparentQuadsDrawn: usize = 0; + +pub fn init() !void { + shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs", &uniforms); + transparentShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/transparent_fragment.fs", &transparentUniforms); + + var rawData: [6*3 << (3*chunk.chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block + const lut = [_]u32{0, 1, 2, 2, 1, 3}; + for(0..rawData.len) |i| { + rawData[i] = @as(u32, @intCast(i))/6*4 + lut[i%6]; + } + + c.glGenVertexArrays(1, &vao); + c.glBindVertexArray(vao); + c.glGenBuffers(1, &vbo); + c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbo); + c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, rawData.len*@sizeOf(u32), &rawData, c.GL_STATIC_DRAW); + c.glBindVertexArray(0); + + faces = try std.ArrayList(u32).initCapacity(main.globalAllocator, 65536); + try faceBuffer.init(main.globalAllocator, 1 << 20, 3); +} + +pub fn deinit() void { + shader.deinit(); + transparentShader.deinit(); + c.glDeleteVertexArrays(1, &vao); + c.glDeleteBuffers(1, &vbo); + faces.deinit(); + faceBuffer.deinit(); +} + +pub fn beginRender() !void { + try faceBuffer.beginRender(); +} + +pub fn endRender() void { + faceBuffer.endRender(); +} + +fn bindCommonUniforms(locations: *UniformStruct, projMatrix: Mat4f, ambient: Vec3f) void { + c.glUniformMatrix4fv(locations.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix)); + + c.glUniform1i(locations.texture_sampler, 0); + c.glUniform1i(locations.emissionSampler, 1); + c.glUniform1i(locations.reflectionMap, 2); + c.glUniform1f(locations.reflectionMapSize, renderer.reflectionCubeMapSize); + + c.glUniformMatrix4fv(locations.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix)); + + c.glUniform3f(locations.ambientLight, ambient[0], ambient[1], ambient[2]); + + c.glUniform1f(locations.zNear, renderer.zNear); + c.glUniform1f(locations.zFar, renderer.zFar); +} + +pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void { + shader.bind(); + + bindCommonUniforms(&uniforms, projMatrix, ambient); + + c.glBindVertexArray(vao); +} + +pub fn bindTransparentShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f) void { + transparentShader.bind(); + + c.glUniform3fv(transparentUniforms.@"fog.color", 1, @ptrCast(&game.fog.color)); + c.glUniform1f(transparentUniforms.@"fog.density", game.fog.density); + + bindCommonUniforms(&transparentUniforms, projMatrix, ambient); + + c.glBindVertexArray(vao); +} + +pub const FaceData = extern struct { + position: packed struct(u32) { + x: u5, + y: u5, + z: u5, + padding: u4 = 0, + isBackFace: bool, + normal: u3, + permutation: u6, + padding2: u3 = 0, + }, + blockAndModel: packed struct(u32) { + typ: u16, + modelIndex: u16, + }, + light: [4]u32 = .{0, 0, 0, 0}, +}; + +const PrimitiveMesh = struct { + coreFaces: std.ArrayListUnmanaged(FaceData) = .{}, + neighborFacesSameLod: [6]std.ArrayListUnmanaged(FaceData) = [_]std.ArrayListUnmanaged(FaceData){.{}} ** 6, + neighborFacesHigherLod: [6]std.ArrayListUnmanaged(FaceData) = [_]std.ArrayListUnmanaged(FaceData){.{}} ** 6, + completeList: []FaceData = &.{}, + coreLen: u32 = 0, + sameLodLens: [6]u32 = .{0} ** 6, + higherLodLens: [6]u32 = .{0} ** 6, + mutex: std.Thread.Mutex = .{}, + bufferAllocation: graphics.SubAllocation = .{.start = 0, .len = 0}, + vertexCount: u31 = 0, + wasChanged: bool = false, + + fn deinit(self: *PrimitiveMesh) void { + faceBuffer.free(self.bufferAllocation) catch unreachable; + self.coreFaces.deinit(main.globalAllocator); + for(&self.neighborFacesSameLod) |*neighborFaces| { + neighborFaces.deinit(main.globalAllocator); + } + for(&self.neighborFacesHigherLod) |*neighborFaces| { + neighborFaces.deinit(main.globalAllocator); + } + main.globalAllocator.free(self.completeList); + } + + fn reset(self: *PrimitiveMesh) void { + self.coreFaces.clearRetainingCapacity(); + for(&self.neighborFacesSameLod) |*neighborFaces| { + neighborFaces.clearRetainingCapacity(); + } + for(&self.neighborFacesHigherLod) |*neighborFaces| { + neighborFaces.clearRetainingCapacity(); + } + } + + fn appendCore(self: *PrimitiveMesh, face: FaceData) !void { + try self.coreFaces.append(main.globalAllocator, face); + } + + fn appendNeighbor(self: *PrimitiveMesh, face: FaceData, neighbor: u3, comptime isLod: bool) !void { + if(isLod) { + try self.neighborFacesHigherLod[neighbor].append(main.globalAllocator, face); + } else { + try self.neighborFacesSameLod[neighbor].append(main.globalAllocator, face); + } + } + + fn clearNeighbor(self: *PrimitiveMesh, neighbor: u3, comptime isLod: bool) void { + if(isLod) { + self.neighborFacesHigherLod[neighbor].clearRetainingCapacity(); + } else { + self.neighborFacesSameLod[neighbor].clearRetainingCapacity(); + } + } + + fn finish(self: *PrimitiveMesh, parent: *ChunkMesh) !void { + var len: usize = self.coreFaces.items.len; + for(self.neighborFacesSameLod) |neighborFaces| { + len += neighborFaces.items.len; + } + for(self.neighborFacesHigherLod) |neighborFaces| { + len += neighborFaces.items.len; + } + const completeList = try main.globalAllocator.alloc(FaceData, len); + var i: usize = 0; + @memcpy(completeList[i..][0..self.coreFaces.items.len], self.coreFaces.items); + i += self.coreFaces.items.len; + for(self.neighborFacesSameLod) |neighborFaces| { + @memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items); + i += neighborFaces.items.len; + } + for(self.neighborFacesHigherLod) |neighborFaces| { + @memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items); + i += neighborFaces.items.len; + } + for(completeList) |*face| { + face.light = getLight(parent, face.position.x, face.position.y, face.position.z, face.position.normal); + } + self.mutex.lock(); + const oldList = self.completeList; + self.completeList = completeList; + self.coreLen = @intCast(self.coreFaces.items.len); + for(self.neighborFacesSameLod, 0..) |neighborFaces, j| { + self.sameLodLens[j] = @intCast(neighborFaces.items.len); + } + for(self.neighborFacesHigherLod, 0..) |neighborFaces, j| { + self.higherLodLens[j] = @intCast(neighborFaces.items.len); + } + self.mutex.unlock(); + main.globalAllocator.free(oldList); + } + + fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 { + const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; + const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; + const z = (wz >> mesh.chunk.voxelSizeShift) & chunk.chunkMask; + const index = chunk.getIndex(x, y, z); + return .{ + 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), + }; + } + + fn getLightAt(parent: *ChunkMesh, x: i32, y: i32, z: i32) [6]u8 { + const wx = parent.pos.wx +% x*parent.pos.voxelSize; + const wy = parent.pos.wy +% y*parent.pos.voxelSize; + const wz = parent.pos.wz +% z*parent.pos.voxelSize; + if(x == x & chunk.chunkMask and y == y & chunk.chunkMask and z == z & chunk.chunkMask) { + return getValues(parent, wx, wy, wz); + } + const neighborMesh = mesh_storage.getMeshFromAnyLodAndIncreaseRefCount(wx, wy, wz, parent.pos.voxelSize) orelse return .{0, 0, 0, 0, 0, 0}; + defer neighborMesh.decreaseRefCount(); + // TODO: If the neighbor mesh has a higher lod the transition isn't seamless. + return getValues(neighborMesh, wx, wy, wz); + } + + fn getLight(parent: *ChunkMesh, x: i32, y: i32, z: i32, normal: u3) [4]u32 { + // TODO: Add a case for non-full cube models. This requires considering more light values along the normal. + const pos = Vec3i{x, y, z}; + var rawVals: [3][3][6]u8 = undefined; + var dx: i32 = -1; + while(dx <= 1): (dx += 1) { + var dy: i32 = -1; + while(dy <= 1): (dy += 1) { + const lightPos = pos +% chunk.Neighbors.textureX[normal]*@as(Vec3i, @splat(dx)) +% chunk.Neighbors.textureY[normal]*@as(Vec3i, @splat(dy)); + rawVals[@intCast(dx + 1)][@intCast(dy + 1)] = getLightAt(parent, lightPos[0], lightPos[1], lightPos[2]); + } + } + var interpolatedVals: [6][4]u32 = undefined; + for(0..6) |channel| { + for(0..2) |destX| { + for(0..2) |destY| { + var val: u32 = 0; + for(0..2) |sourceX| { + for(0..2) |sourceY| { + val += rawVals[destX+sourceX][destY+sourceY][channel]; + } + } + interpolatedVals[channel][destX*2 + destY] = @intCast(val >> 2+3); + } + } + } + var result: [4]u32 = undefined; + for(0..4) |i| { + result[i] = ( + interpolatedVals[0][i] << 25 | + interpolatedVals[1][i] << 20 | + interpolatedVals[2][i] << 15 | + interpolatedVals[3][i] << 10 | + interpolatedVals[4][i] << 5 | + interpolatedVals[5][i] << 0 + ); + } + return result; + } + + fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) !void { + self.mutex.lock(); + defer self.mutex.unlock(); + var len: u32 = self.coreLen; + var offset: u32 = self.coreLen; + var list: [6][]FaceData = undefined; + for(0..6) |i| { + const neighborLen = self.sameLodLens[i]; + if(!isNeighborLod[i]) { + list[i] = self.completeList[offset..][0..neighborLen]; + len += neighborLen; + } + offset += neighborLen; + } + for(0..6) |i| { + const neighborLen = self.higherLodLens[i]; + if(isNeighborLod[i]) { + list[i] = self.completeList[offset..][0..neighborLen]; + len += neighborLen; + } + offset += neighborLen; + } + const fullBuffer = try faceBuffer.allocateAndMapRange(len, &self.bufferAllocation); + defer faceBuffer.unmapRange(fullBuffer); + @memcpy(fullBuffer[0..self.coreLen], self.completeList[0..self.coreLen]); + var i: usize = self.coreLen; + for(0..6) |n| { + @memcpy(fullBuffer[i..][0..list[n].len], list[n]); + i += list[n].len; + } + self.vertexCount = @intCast(6*fullBuffer.len); + self.wasChanged = true; + } + + fn addFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) !void { + if(fromNeighborChunk) |neighbor| { + try self.neighborFacesSameLod[neighbor].append(main.globalAllocator, faceData); + } else { + try self.coreFaces.append(main.globalAllocator, faceData); + } + } + + fn removeFace(self: *PrimitiveMesh, faceData: FaceData, fromNeighborChunk: ?u3) void { + if(fromNeighborChunk) |neighbor| { + var pos: usize = std.math.maxInt(usize); + for(self.neighborFacesSameLod[neighbor].items, 0..) |item, i| { + if(std.meta.eql(faceData, item)) { + pos = i; + break; + } + } + _ = self.neighborFacesSameLod[neighbor].swapRemove(pos); + } else { + var pos: usize = std.math.maxInt(usize); + for(self.coreFaces.items, 0..) |item, i| { + if(std.meta.eql(faceData, item)) { + pos = i; + break; + } + } + _ = self.coreFaces.swapRemove(pos); + } + } +}; + +pub const ChunkMesh = struct { + const SortingData = struct { + face: FaceData, + distance: u32, + isBackFace: bool, + shouldBeCulled: bool, + + pub fn update(self: *SortingData, chunkDx: i32, chunkDy: i32, chunkDz: i32) void { + const x: i32 = self.face.position.x; + const y: i32 = self.face.position.y; + const z: i32 = self.face.position.z; + const dx = x + chunkDx; + const dy = y + chunkDy; + const dz = z + chunkDz; + const normal = self.face.position.normal; + self.isBackFace = self.face.position.isBackFace; + switch(chunk.Neighbors.vectorComponent[normal]) { + .x => { + self.shouldBeCulled = (dx < 0) == (chunk.Neighbors.relX[normal] < 0); + if(dx == 0) { + self.shouldBeCulled = false; + } + }, + .y => { + self.shouldBeCulled = (dy < 0) == (chunk.Neighbors.relY[normal] < 0); + if(dy == 0) { + self.shouldBeCulled = false; + } + }, + .z => { + self.shouldBeCulled = (dz < 0) == (chunk.Neighbors.relZ[normal] < 0); + if(dz == 0) { + self.shouldBeCulled = false; + } + }, + } + const fullDx = dx - chunk.Neighbors.relX[normal]; + const fullDy = dy - chunk.Neighbors.relY[normal]; + const fullDz = dz - chunk.Neighbors.relZ[normal]; + self.distance = @abs(fullDx) + @abs(fullDy) + @abs(fullDz); + } + }; + const BoundingRectToNeighborChunk = struct { + min: Vec3i = @splat(std.math.maxInt(i32)), + max: Vec3i = @splat(0), + + fn adjustToBlock(self: *BoundingRectToNeighborChunk, block: Block, pos: Vec3i, neighbor: u3) void { + if(block.viewThrough()) { + self.min = @min(self.min, pos); + self.max = @max(self.max, pos + chunk.Neighbors.orthogonalComponents[neighbor]); + } + } + }; + pos: chunk.ChunkPosition, + size: i32, + chunk: *chunk.Chunk, + lightingData: *[6]lighting.ChannelChunk, + opaqueMesh: PrimitiveMesh, + transparentMesh: PrimitiveMesh, + lastNeighborsSameLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, + lastNeighborsHigherLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, + isNeighborLod: [6]bool = .{false} ** 6, + visibilityMask: u8 = 0xff, + currentSorting: []SortingData = &.{}, + sortingOutputBuffer: []FaceData = &.{}, + culledSortingCount: u31 = 0, + lastTransparentUpdatePos: Vec3i = Vec3i{0, 0, 0}, + refCount: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), + needsLightRefresh: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), + needsMeshUpdate: bool = false, + finishedMeshing: bool = false, + mutex: std.Thread.Mutex = .{}, + + chunkBorders: [6]BoundingRectToNeighborChunk = [1]BoundingRectToNeighborChunk{.{}} ** 6, + + pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) !void { + const lightingData = try main.globalAllocator.create([6]lighting.ChannelChunk); + try lightingData[0].init(ch, .sun_red); + try lightingData[1].init(ch, .sun_green); + try lightingData[2].init(ch, .sun_blue); + try lightingData[3].init(ch, .red); + try lightingData[4].init(ch, .green); + try lightingData[5].init(ch, .blue); + self.* = ChunkMesh{ + .pos = pos, + .size = chunk.chunkSize*pos.voxelSize, + .opaqueMesh = .{}, + .transparentMesh = .{}, + .chunk = ch, + .lightingData = lightingData, + }; + } + + pub fn deinit(self: *ChunkMesh) void { + std.debug.assert(self.refCount.load(.Monotonic) == 0); + self.opaqueMesh.deinit(); + self.transparentMesh.deinit(); + main.globalAllocator.free(self.currentSorting); + main.globalAllocator.free(self.sortingOutputBuffer); + main.globalAllocator.destroy(self.chunk); + main.globalAllocator.destroy(self.lightingData); + } + + pub fn increaseRefCount(self: *ChunkMesh) void { + const prevVal = self.refCount.fetchAdd(1, .Monotonic); + std.debug.assert(prevVal != 0); + } + + /// In cases where it's not certain whether the thing was cleared already. + pub fn tryIncreaseRefCount(self: *ChunkMesh) bool { + var prevVal = self.refCount.load(.Monotonic); + while(prevVal != 0) { + prevVal = self.refCount.cmpxchgWeak(prevVal, prevVal + 1, .Monotonic, .Monotonic) orelse return true; + } + return false; + } + + pub fn decreaseRefCount(self: *ChunkMesh) void { + const prevVal = self.refCount.fetchSub(1, .Monotonic); + std.debug.assert(prevVal != 0); + if(prevVal == 1) { + mesh_storage.addMeshToClearListAndDecreaseRefCount(self) catch @panic("Out of Memory"); + } + } + + pub fn scheduleLightRefreshAndDecreaseRefCount(self: *ChunkMesh) !void { + if(!self.needsLightRefresh.swap(true, .AcqRel)) { + try LightRefreshTask.scheduleAndDecreaseRefCount(self); + } else { + self.decreaseRefCount(); + } + } + const LightRefreshTask = struct { + mesh: *ChunkMesh, + + pub const vtable = main.utils.ThreadPool.VTable{ + .getPriority = @ptrCast(&getPriority), + .isStillNeeded = @ptrCast(&isStillNeeded), + .run = @ptrCast(&run), + .clean = @ptrCast(&clean), + }; + + pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) !void { + const task = try main.globalAllocator.create(LightRefreshTask); + task.* = .{ + .mesh = mesh, + }; + try main.threadPool.addTask(task, &vtable); + } + + pub fn getPriority(_: *LightRefreshTask) f32 { + return 1000000; + } + + pub fn isStillNeeded(_: *LightRefreshTask) bool { + return true; // TODO: Is it worth checking for this? + } + + pub fn run(self: *LightRefreshTask) Allocator.Error!void { + if(self.mesh.needsLightRefresh.swap(false, .AcqRel)) { + self.mesh.mutex.lock(); + try self.mesh.finishData(); + self.mesh.mutex.unlock(); + try mesh_storage.addToUpdateListAndDecreaseRefCount(self.mesh); + } else { + self.mesh.decreaseRefCount(); + } + main.globalAllocator.destroy(self); + } + + pub fn clean(self: *LightRefreshTask) void { + self.mesh.decreaseRefCount(); + main.globalAllocator.destroy(self); + } + }; + + pub fn isEmpty(self: *const ChunkMesh) bool { + return self.opaqueMesh.vertexCount == 0 and self.transparentMesh.vertexCount == 0; + } + + fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool { + const rotatedModel = blocks.meshes.model(block); + const model = &models.models.items[rotatedModel.modelIndex]; + const freestandingModel = rotatedModel.modelIndex != models.fullCube and switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) { + chunk.Neighbors.dirNegX => model.min[0] != 0, + chunk.Neighbors.dirPosX => model.max[0] != 16, // TODO: Use a bitfield inside the models or something like that. + chunk.Neighbors.dirDown => model.min[1] != 0, + chunk.Neighbors.dirUp => model.max[1] != 16, + chunk.Neighbors.dirNegZ => model.min[2] != 0, + chunk.Neighbors.dirPosZ => model.max[2] != 16, + else => unreachable, + }; + return block.typ != 0 and ( + freestandingModel + or other.typ == 0 + or (!std.meta.eql(block, other) and other.viewThrough()) + or blocks.meshes.model(other).modelIndex != 0 // TODO: make this more strict to avoid overdraw. + ); + } + + pub fn regenerateMainMesh(self: *ChunkMesh) !void { + try mesh_storage.addMeshToStorage(self); + 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 < chunk.chunkSize): (x += 1) { + var y: u8 = 0; + while(y < chunk.chunkSize): (y += 1) { + var z: u8 = 0; + while(z < chunk.chunkSize): (z += 1) { + const block = (&self.chunk.blocks)[chunk.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(chunk.Neighbors.iterable) |i| { + n += 1; + const x2 = x + chunk.Neighbors.relX[i]; + const y2 = y + chunk.Neighbors.relY[i]; + const z2 = z + chunk.Neighbors.relZ[i]; + if(x2&chunk.chunkMask != x2 or y2&chunk.chunkMask != y2 or z2&chunk.chunkMask != z2) continue; // Neighbor is outside the chunk. + const neighborBlock = (&self.chunk.blocks)[chunk.getIndex(x2, y2, z2)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) { + if(block.transparent()) { + if(block.hasBackFace()) { + try self.transparentMesh.appendCore(constructFaceData(block, i ^ 1, x, y, z, true)); + } + try self.transparentMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false)); + } else { + try self.opaqueMesh.appendCore(constructFaceData(block, i, x2, y2, z2, false)); // TODO: Create multiple faces for non-cube meshes. + } + } + } + } + } + } + // Check out the borders: + x = 0; + while(x < chunk.chunkSize): (x += 1) { + var y: u8 = 0; + while(y < chunk.chunkSize): (y += 1) { + self.chunkBorders[chunk.Neighbors.dirNegX].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(0, x, y)], .{0, x, y}, chunk.Neighbors.dirNegX); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[chunk.Neighbors.dirPosX].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(chunk.chunkSize-1, x, y)], .{chunk.chunkSize, x, y}, chunk.Neighbors.dirPosX); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[chunk.Neighbors.dirDown].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(x, 0, y)], .{x, 0, y}, chunk.Neighbors.dirDown); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[chunk.Neighbors.dirUp].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(x, chunk.chunkSize-1, y)], .{x, chunk.chunkSize, y}, chunk.Neighbors.dirUp); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[chunk.Neighbors.dirNegZ].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(x, y, 0)], .{x, y, 0}, chunk.Neighbors.dirNegZ); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[chunk.Neighbors.dirPosZ].adjustToBlock((&self.chunk.blocks)[chunk.getIndex(x, y, chunk.chunkSize-1)], .{x, y, chunk.chunkSize}, chunk.Neighbors.dirPosZ); // TODO: Wait for the compiler bug to get fixed. + } + } + self.mutex.unlock(); + for(self.lightingData[3..]) |*lightingData| { + try lightingData.propagateLights(lightEmittingBlocks.items, true); + } + sunLight: { + var sunStarters: [chunk.chunkSize*chunk.chunkSize][3]u8 = undefined; + var index: usize = 0; + const lightStartMap = mesh_storage.getLightMapPieceAndIncreaseRefCount(self.pos.wx, self.pos.wz, self.pos.voxelSize) orelse break :sunLight; + defer lightStartMap.decreaseRefCount(); + x = 0; + while(x < chunk.chunkSize): (x += 1) { + var z: u8 = 0; + while(z < chunk.chunkSize): (z += 1) { + const startHeight: i32 = lightStartMap.getHeight(self.pos.wx + x*self.pos.voxelSize, self.pos.wz + z*self.pos.voxelSize); + const relHeight = startHeight -% self.pos.wy; + if(relHeight < chunk.chunkSize*self.pos.voxelSize) { + sunStarters[index] = .{x, chunk.chunkSize-1, z}; + index += 1; + } + } + } + for(self.lightingData[0..3]) |*lightingData| { + try lightingData.propagateLights(sunStarters[0..index], true); + } + } + + // TODO: Sunlight propagation + try self.finishNeighbors(false); + } + + fn addFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) !void { + if(transparent) { + try self.transparentMesh.addFace(faceData, fromNeighborChunk); + } else { + try self.opaqueMesh.addFace(faceData, fromNeighborChunk); + } + } + + fn removeFace(self: *ChunkMesh, faceData: FaceData, fromNeighborChunk: ?u3, transparent: bool) void { + if(transparent) { + self.transparentMesh.removeFace(faceData, fromNeighborChunk); + } else { + self.opaqueMesh.removeFace(faceData, fromNeighborChunk); + } + } + + pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, newBlock: Block) !void { + self.mutex.lock(); + const x = _x & chunk.chunkMask; + const y = _y & chunk.chunkMask; + const z = _z & chunk.chunkMask; + const oldBlock = self.chunk.blocks[chunk.getIndex(x, y, z)]; + self.chunk.blocks[chunk.getIndex(x, y, z)] = newBlock; + self.mutex.unlock(); + for(self.lightingData[0..]) |*lightingData| { + try lightingData.propagateLightsDestructive(&.{.{@intCast(x), @intCast(y), @intCast(z)}}); + } + if(newBlock.light() != 0) { + for(self.lightingData[3..]) |*lightingData| { + try lightingData.propagateLights(&.{.{@intCast(x), @intCast(y), @intCast(z)}}, false); + } + } + self.mutex.lock(); + defer self.mutex.unlock(); + for(chunk.Neighbors.iterable) |neighbor| { + var neighborMesh = self; + var nx = x + chunk.Neighbors.relX[neighbor]; + var ny = y + chunk.Neighbors.relY[neighbor]; + var nz = z + chunk.Neighbors.relZ[neighbor]; + if(nx & chunk.chunkMask != nx or ny & chunk.chunkMask != ny or nz & chunk.chunkMask != nz) { // Outside this chunk. + neighborMesh = mesh_storage.getNeighborFromRenderThread(self.pos, self.pos.voxelSize, neighbor) orelse continue; + } + if(neighborMesh != self) { + self.mutex.unlock(); + deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); + } + defer if(neighborMesh != self) neighborMesh.mutex.unlock(); + nx &= chunk.chunkMask; + ny &= chunk.chunkMask; + nz &= chunk.chunkMask; + const neighborBlock = neighborMesh.chunk.blocks[chunk.getIndex(nx, ny, nz)]; + { // TODO: Batch all the changes and apply them in one go for more efficiency. + { // The face of the changed block + const newVisibility = canBeSeenThroughOtherBlock(newBlock, neighborBlock, neighbor); + const oldVisibility = canBeSeenThroughOtherBlock(oldBlock, neighborBlock, neighbor); + if(oldVisibility) { // Removing the face + const faceData = constructFaceData(oldBlock, neighbor, nx, ny, nz, false); + if(neighborMesh == self) { + self.removeFace(faceData, null, oldBlock.transparent()); + } else { + neighborMesh.removeFace(faceData, neighbor ^ 1, oldBlock.transparent()); + } + if(oldBlock.hasBackFace()) { + const backFaceData = constructFaceData(oldBlock, neighbor ^ 1, x, y, z, true); + if(neighborMesh == self) { + self.removeFace(backFaceData, null, true); + } else { + self.removeFace(backFaceData, neighbor, true); + } + } + } + if(newVisibility) { // Adding the face + const faceData = constructFaceData(newBlock, neighbor, nx, ny, nz, false); + if(neighborMesh == self) { + try self.addFace(faceData, null, newBlock.transparent()); + } else { + try neighborMesh.addFace(faceData, neighbor ^ 1, newBlock.transparent()); + } + if(newBlock.hasBackFace()) { + const backFaceData = constructFaceData(newBlock, neighbor ^ 1, x, y, z, true); + if(neighborMesh == self) { + try self.addFace(backFaceData, null, true); + } else { + try self.addFace(backFaceData, neighbor, true); + } + } + } + } + { // The face of the neighbor block + const newVisibility = canBeSeenThroughOtherBlock(neighborBlock, newBlock, neighbor ^ 1); + if(canBeSeenThroughOtherBlock(neighborBlock, oldBlock, neighbor ^ 1) != newVisibility) { + if(newVisibility) { // Adding the face + const faceData = constructFaceData(neighborBlock, neighbor ^ 1, x, y, z, false); + if(neighborMesh == self) { + try self.addFace(faceData, null, neighborBlock.transparent()); + } else { + try self.addFace(faceData, neighbor, neighborBlock.transparent()); + } + if(neighborBlock.hasBackFace()) { + const backFaceData = constructFaceData(neighborBlock, neighbor, nx, ny, nz, true); + if(neighborMesh == self) { + try self.addFace(backFaceData, null, true); + } else { + try neighborMesh.addFace(backFaceData, neighbor ^ 1, true); + } + } + } else { // Removing the face + const faceData = constructFaceData(neighborBlock, neighbor ^ 1, x, y, z, false); + if(neighborMesh == self) { + self.removeFace(faceData, null, neighborBlock.transparent()); + } else { + self.removeFace(faceData, neighbor, neighborBlock.transparent()); + } + if(neighborBlock.hasBackFace()) { + const backFaceData = constructFaceData(neighborBlock, neighbor, nx, ny, nz, true); + if(neighborMesh == self) { + self.removeFace(backFaceData, null, true); + } else { + neighborMesh.removeFace(backFaceData, neighbor ^ 1, true); + } + } + } + } + } + } + if(neighborMesh != self) { + _ = neighborMesh.needsLightRefresh.swap(false, .AcqRel); + try neighborMesh.finishData(); + try neighborMesh.uploadData(); + } + } + _ = self.needsLightRefresh.swap(false, .AcqRel); + try self.finishData(); + try self.uploadData(); + } + + pub inline fn constructFaceData(block: Block, normal: u3, x: i32, y: i32, z: i32, comptime backFace: bool) FaceData { + const model = blocks.meshes.model(block); + return FaceData { + .position = .{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .normal = normal, .permutation = model.permutation.toInt(), .isBackFace = backFace}, + .blockAndModel = .{.typ = block.typ, .modelIndex = model.modelIndex}, + }; + } + + fn clearNeighbor(self: *ChunkMesh, neighbor: u3, comptime isLod: bool) void { + self.opaqueMesh.clearNeighbor(neighbor, isLod); + self.transparentMesh.clearNeighbor(neighbor, isLod); + } + + pub fn finishData(self: *ChunkMesh) !void { + std.debug.assert(!self.mutex.tryLock()); + try self.opaqueMesh.finish(self); + try self.transparentMesh.finish(self); + } + + pub fn uploadData(self: *ChunkMesh) !void { + try self.opaqueMesh.uploadData(self.isNeighborLod); + try self.transparentMesh.uploadData(self.isNeighborLod); + } + + pub fn changeLodBorders(self: *ChunkMesh, isNeighborLod: [6]bool) !void { + if(!std.meta.eql(isNeighborLod, self.isNeighborLod)) { + self.isNeighborLod = isNeighborLod; + try self.uploadData(); + } + } + + fn deadlockFreeDoubleLock(m1: *std.Thread.Mutex, m2: *std.Thread.Mutex) void { + if(@intFromPtr(m1) < @intFromPtr(m2)) { + m1.lock(); + m2.lock(); + } else { + m2.lock(); + m1.lock(); + } + } + + fn finishNeighbors(self: *ChunkMesh, comptime inRenderThread: bool) !void { + const getNeighborMesh: fn(chunk.ChunkPosition, u31, u3) ?*ChunkMesh = if(inRenderThread) mesh_storage.getNeighborFromRenderThread else mesh_storage.getNeighborAndIncreaseRefCount; + for(chunk.Neighbors.iterable) |neighbor| { + const nullNeighborMesh = getNeighborMesh(self.pos, self.pos.voxelSize, neighbor); + if(nullNeighborMesh) |neighborMesh| sameLodBlock: { + defer if(!inRenderThread) neighborMesh.decreaseRefCount(); + std.debug.assert(neighborMesh != self); + deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); + defer self.mutex.unlock(); + defer neighborMesh.mutex.unlock(); + if(self.lastNeighborsSameLod[neighbor] == neighborMesh) break :sameLodBlock; + self.lastNeighborsSameLod[neighbor] = neighborMesh; + neighborMesh.lastNeighborsSameLod[neighbor ^ 1] = self; + self.clearNeighbor(neighbor, false); + neighborMesh.clearNeighbor(neighbor ^ 1, false); + 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 block = (&self.chunk.blocks)[chunk.getIndex(x, y, z)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + const otherBlock = (&neighborMesh.chunk.blocks)[chunk.getIndex(otherX, otherY, otherZ)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { + if(block.transparent()) { + if(block.hasBackFace()) { + try self.transparentMesh.appendNeighbor(constructFaceData(block, neighbor ^ 1, x, y, z, true), neighbor, false); + } + try neighborMesh.transparentMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false); + } else { + try neighborMesh.opaqueMesh.appendNeighbor(constructFaceData(block, neighbor, otherX, otherY, otherZ, false), neighbor ^ 1, false); + } + } + if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) { + if(otherBlock.transparent()) { + if(otherBlock.hasBackFace()) { + try neighborMesh.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor, otherX, otherY, otherZ, true), neighbor ^ 1, false); + } + try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false); + } else { + try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, false); + } + } + } + } + _ = neighborMesh.needsLightRefresh.swap(false, .AcqRel); + try neighborMesh.finishData(); + if(inRenderThread) { + try neighborMesh.uploadData(); + } else { + neighborMesh.increaseRefCount(); + try mesh_storage.addToUpdateListAndDecreaseRefCount(neighborMesh); + } + } else { + self.mutex.lock(); + defer self.mutex.unlock(); + if(self.lastNeighborsSameLod[neighbor] != null) { + self.clearNeighbor(neighbor, false); + self.lastNeighborsSameLod[neighbor] = null; + } + } + // lod border: + if(self.pos.voxelSize == 1 << settings.highestLOD) continue; + const neighborMesh = getNeighborMesh(self.pos, 2*self.pos.voxelSize, neighbor) orelse { + self.mutex.lock(); + defer self.mutex.unlock(); + if(self.lastNeighborsHigherLod[neighbor] != null) { + self.clearNeighbor(neighbor, true); + self.lastNeighborsHigherLod[neighbor] = null; + } + continue; + }; + deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex); + defer self.mutex.unlock(); + defer neighborMesh.mutex.unlock(); + defer if(!inRenderThread) neighborMesh.decreaseRefCount(); + if(self.lastNeighborsHigherLod[neighbor] == neighborMesh) continue; + self.lastNeighborsHigherLod[neighbor] = neighborMesh; + self.clearNeighbor(neighbor, true); + const x3: i32 = if(neighbor & 1 == 0) chunk.chunkMask else 0; + const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunk.chunkSize; + const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunk.chunkSize; + const offsetZ = @divExact(self.pos.wz, self.pos.voxelSize) & chunk.chunkSize; + 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]+%offsetX >> 1) & chunk.chunkMask; + const otherY = (y+%chunk.Neighbors.relY[neighbor]+%offsetY >> 1) & chunk.chunkMask; + const otherZ = (z+%chunk.Neighbors.relZ[neighbor]+%offsetZ >> 1) & chunk.chunkMask; + const block = (&self.chunk.blocks)[chunk.getIndex(x, y, z)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + const otherBlock = (&neighborMesh.chunk.blocks)[chunk.getIndex(otherX, otherY, otherZ)]; // ← a temporary fix to a compiler performance bug. TODO: check if this was fixed. + if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) { + if(otherBlock.transparent()) { + try self.transparentMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true); + } else { + try self.opaqueMesh.appendNeighbor(constructFaceData(otherBlock, neighbor ^ 1, x, y, z, false), neighbor, true); + } + } + if(block.hasBackFace()) { + if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { + try self.transparentMesh.appendNeighbor(constructFaceData(block, neighbor ^ 1, x, y, z, true), neighbor, true); + } + } + } + } + } + self.mutex.lock(); + defer self.mutex.unlock(); + _ = self.needsLightRefresh.swap(false, .AcqRel); + try self.finishData(); + } + + pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { + if(self.opaqueMesh.vertexCount == 0) return; + c.glUniform3f( + uniforms.modelPosition, + @floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]), + @floatCast(@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1]), + @floatCast(@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2]) + ); + c.glUniform1i(uniforms.visibilityMask, self.visibilityMask); + c.glUniform1i(uniforms.voxelSize, self.pos.voxelSize); + quadsDrawn += self.opaqueMesh.vertexCount/6; + c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.opaqueMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.opaqueMesh.bufferAllocation.start*4); + } + + pub fn renderTransparent(self: *ChunkMesh, playerPosition: Vec3d) !void { + if(self.transparentMesh.vertexCount == 0) return; + + var needsUpdate: bool = false; + if(self.transparentMesh.wasChanged) { + self.transparentMesh.wasChanged = false; + self.transparentMesh.mutex.lock(); + defer self.transparentMesh.mutex.unlock(); + var len: usize = self.transparentMesh.coreLen; + var offset: usize = self.transparentMesh.coreLen; + var list: [6][]FaceData = undefined; + for(0..6) |i| { + const neighborLen = self.transparentMesh.sameLodLens[i]; + if(!self.isNeighborLod[i]) { + list[i] = self.transparentMesh.completeList[offset..][0..neighborLen]; + len += neighborLen; + } + offset += neighborLen; + } + for(0..6) |i| { + const neighborLen = self.transparentMesh.higherLodLens[i]; + if(self.isNeighborLod[i]) { + list[i] = self.transparentMesh.completeList[offset..][0..neighborLen]; + len += neighborLen; + } + offset += neighborLen; + } + self.sortingOutputBuffer = try main.globalAllocator.realloc(self.sortingOutputBuffer, len); + self.currentSorting = try main.globalAllocator.realloc(self.currentSorting, len); + for(0..self.transparentMesh.coreLen) |i| { + self.currentSorting[i].face = self.transparentMesh.completeList[i]; + } + offset = self.transparentMesh.coreLen; + for(0..6) |n| { + for(0..list[n].len) |i| { + self.currentSorting[offset + i].face = list[n][i]; + } + offset += list[n].len; + } + + needsUpdate = true; + } + + var relativePos = Vec3d { + @as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0], + @as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1], + @as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2] + }/@as(Vec3d, @splat(@as(f64, @floatFromInt(self.pos.voxelSize)))); + relativePos = @min(relativePos, @as(Vec3d, @splat(0))); + relativePos = @max(relativePos, @as(Vec3d, @splat(-32))); + const updatePos: Vec3i = @intFromFloat(relativePos); + if(@reduce(.Or, updatePos != self.lastTransparentUpdatePos)) { + self.lastTransparentUpdatePos = updatePos; + needsUpdate = true; + } + if(needsUpdate) { + for(self.currentSorting) |*val| { + val.update( + updatePos[0], + updatePos[1], + updatePos[2], + ); + } + + // Sort by back vs front face: + { + var backFaceStart: usize = 0; + var i: usize = 0; + var culledStart: usize = self.currentSorting.len; + while(culledStart > 0) { + if(!self.currentSorting[culledStart-1].shouldBeCulled) { + break; + } + culledStart -= 1; + } + while(i < culledStart): (i += 1) { + if(self.currentSorting[i].shouldBeCulled) { + culledStart -= 1; + std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[culledStart]); + while(culledStart > 0) { + if(!self.currentSorting[culledStart-1].shouldBeCulled) { + break; + } + culledStart -= 1; + } + } + if(!self.currentSorting[i].isBackFace) { + std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[backFaceStart]); + backFaceStart += 1; + } + } + self.culledSortingCount = @intCast(culledStart); + } + + // Sort it using bucket sort: + var buckets: [34*3]u32 = undefined; + @memset(&buckets, 0); + for(self.currentSorting[0..self.culledSortingCount]) |val| { + buckets[34*3 - 1 - val.distance] += 1; + } + var prefixSum: u32 = 0; + for(&buckets) |*val| { + const copy = val.*; + val.* = prefixSum; + prefixSum += copy; + } + // Move it over into a new buffer: + for(0..self.culledSortingCount) |i| { + const bucket = 34*3 - 1 - self.currentSorting[i].distance; + self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face; + buckets[bucket] += 1; + } + + // Upload: + try faceBuffer.uploadData(self.sortingOutputBuffer[0..self.culledSortingCount], &self.transparentMesh.bufferAllocation); + } + + c.glUniform3f( + transparentUniforms.modelPosition, + @floatCast(@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0]), + @floatCast(@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1]), + @floatCast(@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2]) + ); + c.glUniform1i(transparentUniforms.visibilityMask, self.visibilityMask); + c.glUniform1i(transparentUniforms.voxelSize, self.pos.voxelSize); + transparentQuadsDrawn += self.culledSortingCount; + c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.culledSortingCount*6, c.GL_UNSIGNED_INT, null, self.transparentMesh.bufferAllocation.start*4); + } +}; \ No newline at end of file diff --git a/src/lighting.zig b/src/renderer/lighting.zig similarity index 93% rename from src/lighting.zig rename to src/renderer/lighting.zig index 95114ac7..cf6d4a73 100644 --- a/src/lighting.zig +++ b/src/renderer/lighting.zig @@ -4,6 +4,8 @@ const Atomic = std.atomic.Value; const main = @import("root"); const blocks = main.blocks; const chunk = main.chunk; +const chunk_meshing = @import("chunk_meshing.zig"); +const mesh_storage = @import("mesh_storage.zig"); const Channel = enum(u8) { sun_red = 0, @@ -56,7 +58,7 @@ pub const ChannelChunk = struct { }; const ChunkEntries = struct { - mesh: ?*chunk.meshing.ChunkMesh, + mesh: ?*chunk_meshing.ChunkMesh, entries: std.ArrayListUnmanaged(PositionEntry), }; @@ -95,13 +97,13 @@ pub const ChannelChunk = struct { } } self.mutex.unlock(); - if(main.renderer.RenderStructure.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { + if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { try mesh.scheduleLightRefreshAndDecreaseRefCount(); } 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; + const neighborMesh = mesh_storage.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); } @@ -150,13 +152,13 @@ pub const ChannelChunk = struct { } } self.mutex.unlock(); - if(main.renderer.RenderStructure.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { + if(mesh_storage.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| { try mesh.scheduleLightRefreshAndDecreaseRefCount(); } 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; + const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, @intCast(neighbor)) orelse continue; try constructiveEntries.append(main.globalAllocator, .{ .mesh = neighborMesh, .entries = try neighborMesh.lightingData[@intFromEnum(self.channel)].propagateDestructiveFromNeighbor(neighborLists[neighbor].items, constructiveEntries), @@ -231,7 +233,7 @@ pub const ChannelChunk = struct { 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; + const neighborMesh = mesh_storage.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); diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig new file mode 100644 index 00000000..8186e15c --- /dev/null +++ b/src/renderer/mesh_storage.zig @@ -0,0 +1,1058 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Atomic = std.atomic.Value; + +const main = @import("root"); +const blocks = main.blocks; +const chunk = main.chunk; +const game = main.game; +const network = main.network; +const settings = main.settings; +const utils = main.utils; +const LightMap = main.server.terrain.LightMap; +const vec = main.vec; +const Vec2f = vec.Vec2f; +const Vec3i = vec.Vec3i; +const Vec3f = vec.Vec3f; +const Vec3d = vec.Vec3d; +const Vec4f = vec.Vec4f; +const Mat4f = vec.Mat4f; + +const chunk_meshing = @import("chunk_meshing.zig"); + + + +const ChunkMeshNode = struct { + mesh: Atomic(?*chunk_meshing.ChunkMesh), + lod: u3, + min: Vec2f, + max: Vec2f, + active: bool, + rendered: bool, +}; +const storageSize = 32; +const storageMask = storageSize - 1; +var storageLists: [settings.highestLOD + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined; +var mapStorageLists: [settings.highestLOD + 1]*[storageSize*storageSize]Atomic(?*LightMap.LightMapFragment) = undefined; +var meshList = std.ArrayList(*chunk_meshing.ChunkMesh).init(main.globalAllocator); +var priorityMeshUpdateList = std.ArrayList(*chunk_meshing.ChunkMesh).init(main.globalAllocator); +pub var updatableList = std.ArrayList(*chunk_meshing.ChunkMesh).init(main.globalAllocator); +var mapUpdatableList = std.ArrayList(*LightMap.LightMapFragment).init(main.globalAllocator); +var clearList = std.ArrayList(*chunk_meshing.ChunkMesh).init(main.globalAllocator); +var lastPx: i32 = 0; +var lastPy: i32 = 0; +var lastPz: i32 = 0; +var lastRD: i32 = 0; +var mutex = std.Thread.Mutex{}; +var blockUpdateMutex = std.Thread.Mutex{}; +const BlockUpdate = struct { + x: i32, + y: i32, + z: i32, + newBlock: blocks.Block, +}; +var blockUpdateList: std.ArrayList(BlockUpdate) = undefined; + +pub fn init() !void { + lastRD = 0; + blockUpdateList = std.ArrayList(BlockUpdate).init(main.globalAllocator); + for(&storageLists) |*storageList| { + storageList.* = try main.globalAllocator.create([storageSize*storageSize*storageSize]ChunkMeshNode); + for(storageList.*) |*val| { + val.mesh = Atomic(?*chunk_meshing.ChunkMesh).init(null); + val.rendered = false; + } + } + for(&mapStorageLists) |*mapStorageList| { + mapStorageList.* = try main.globalAllocator.create([storageSize*storageSize]Atomic(?*LightMap.LightMapFragment)); + @memset(mapStorageList.*, Atomic(?*LightMap.LightMapFragment).init(null)); + } +} + +pub fn deinit() void { + const olderPx = lastPx; + const olderPy = lastPy; + const olderPz = lastPz; + const olderRD = lastRD; + lastPx = 0; + lastPy = 0; + lastPz = 0; + lastRD = 0; + freeOldMeshes(olderPx, olderPy, olderPz, olderRD) catch |err| { + std.log.err("Error while freeing remaining meshes: {s}", .{@errorName(err)}); + }; + for(storageLists) |storageList| { + main.globalAllocator.destroy(storageList); + } + for(mapStorageLists) |mapStorageList| { + main.globalAllocator.destroy(mapStorageList); + } + for(updatableList.items) |mesh| { + mesh.decreaseRefCount(); + } + updatableList.deinit(); + for(mapUpdatableList.items) |map| { + map.decreaseRefCount(); + } + mapUpdatableList.deinit(); + for(priorityMeshUpdateList.items) |mesh| { + mesh.decreaseRefCount(); + } + priorityMeshUpdateList.deinit(); + blockUpdateList.deinit(); + meshList.deinit(); + for(clearList.items) |mesh| { + mesh.deinit(); + main.globalAllocator.destroy(mesh); + } + clearList.deinit(); +} + +fn getNodeFromRenderThread(pos: chunk.ChunkPosition) *ChunkMeshNode { + const lod = std.math.log2_int(u31, pos.voxelSize); + var xIndex = pos.wx >> lod+chunk.chunkShift; + var yIndex = pos.wy >> lod+chunk.chunkShift; + var zIndex = pos.wz >> lod+chunk.chunkShift; + xIndex &= storageMask; + yIndex &= storageMask; + zIndex &= storageMask; + const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; + return &storageLists[lod][@intCast(index)]; +} + +fn getMapPieceLocation(x: i32, z: i32, voxelSize: u31) *Atomic(?*LightMap.LightMapFragment) { + const lod = std.math.log2_int(u31, voxelSize); + var xIndex = x >> lod+LightMap.LightMapFragment.mapShift; + var zIndex = z >> lod+LightMap.LightMapFragment.mapShift; + xIndex &= storageMask; + zIndex &= storageMask; + const index = xIndex*storageSize + zIndex; + return &(&mapStorageLists)[lod][@intCast(index)]; +} + +pub fn getLightMapPieceAndIncreaseRefCount(x: i32, z: i32, voxelSize: u31) ?*LightMap.LightMapFragment { + const result: *LightMap.LightMapFragment = getMapPieceLocation(x, z, voxelSize).load(.Acquire) orelse return null; + var refCount: u16 = 1; + while(result.refCount.cmpxchgWeak(refCount, refCount+1, .Monotonic, .Monotonic)) |otherVal| { + if(otherVal == 0) return null; + refCount = otherVal; + } + return result; +} + +pub fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block { + const node = getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=1}); + const mesh = node.mesh.load(.Acquire) orelse return null; + const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask); + return block; +} + +pub fn getBlockFromAnyLodFromRenderThread(x: i32, y: i32, z: i32) blocks.Block { + var lod: u5 = 0; + while(lod < settings.highestLOD) : (lod += 1) { + const node = getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=@as(u31, 1) << lod}); + const mesh = node.mesh.load(.Acquire) orelse continue; + const block = mesh.chunk.getBlock(x & chunk.chunkMask<= maxX) return false; + var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx); + deltaX = @max(0, deltaX - size/2); + + const maxYRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX); + const minY = lastPy-%maxYRenderDistance & invMask; + const maxY = lastPy+%maxYRenderDistance+%size & invMask; + if(pos.wy < minY) return false; + if(pos.wy >= maxY) return false; + var deltaY: i64 = @abs(pos.wy +% size/2 -% lastPy); + deltaY = @max(0, deltaY - size/2); + + const maxZRenderDistance: i32 = reduceRenderDistance(maxYRenderDistance, deltaY); + if(maxZRenderDistance == 0) return false; + const minZ = lastPz-%maxZRenderDistance & invMask; + const maxZ = lastPz+%maxZRenderDistance+%size & invMask; + if(pos.wz < minZ) return false; + if(pos.wz >= maxZ) return false; + return true; +} + +fn isMapInRenderDistance(pos: LightMap.MapFragmentPosition) bool { + const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize; + const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize)*pos.voxelSize; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + const minX = lastPx-%maxRenderDistance & invMask; + const maxX = lastPx+%maxRenderDistance+%size & invMask; + if(pos.wx < minX) return false; + if(pos.wx >= maxX) return false; + var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx); + deltaX = @max(0, deltaX - size/2); + + const maxZRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX); + if(maxZRenderDistance == 0) return false; + const minZ = lastPz-%maxZRenderDistance & invMask; + const maxZ = lastPz+%maxZRenderDistance+%size & invMask; + if(pos.wz < minZ) return false; + if(pos.wz >= maxZ) return false; + return true; +} + +fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32) !void { + for(0..storageLists.len) |_lod| { + const lod: u5 = @intCast(_lod); + const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; + const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; + const size: u31 = chunk.chunkSize << lod; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); + + const minX = olderPx-%maxRenderDistanceOld & invMask; + const maxX = olderPx+%maxRenderDistanceOld+%size & invMask; + var x = minX; + while(x != maxX): (x +%= size) { + const xIndex = @divExact(x, size) & storageMask; + var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); + deltaXNew = @max(0, deltaXNew - size/2); + var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); + deltaXOld = @max(0, deltaXOld - size/2); + const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); + const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); + + const minY = olderPy-%maxYRenderDistanceOld & invMask; + const maxY = olderPy+%maxYRenderDistanceOld+%size & invMask; + var y = minY; + while(y != maxY): (y +%= size) { + const yIndex = @divExact(y, size) & storageMask; + var deltaYOld: i64 = @abs(y +% size/2 -% olderPy); + deltaYOld = @max(0, deltaYOld - size/2); + var deltaYNew: i64 = @abs(y +% size/2 -% lastPy); + deltaYNew = @max(0, deltaYNew - size/2); + var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld); + if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; + var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew); + if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; + + const minZOld = olderPz-%maxZRenderDistanceOld & invMask; + const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; + const minZNew = lastPz-%maxZRenderDistanceNew & invMask; + const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; + + var zValues: [storageSize]i32 = undefined; + var zValuesLen: usize = 0; + if(minZNew -% minZOld > 0) { + var z = minZOld; + while(z != minZNew and z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZOld -% maxZNew > 0) { + var z = minZOld +% @max(0, maxZNew -% minZOld); + while(z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; + + const node = &storageLists[_lod][@intCast(index)]; + if(node.mesh.load(.Acquire)) |mesh| { + mesh.decreaseRefCount(); + node.mesh.store(null, .Release); + } + } + } + } + } + for(0..mapStorageLists.len) |_lod| { + const lod: u5 = @intCast(_lod); + const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; + const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; + const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); + + const minX = olderPx-%maxRenderDistanceOld & invMask; + const maxX = olderPx+%maxRenderDistanceOld+%size & invMask; + var x = minX; + while(x != maxX): (x +%= size) { + const xIndex = @divExact(x, size) & storageMask; + var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); + deltaXNew = @max(0, deltaXNew - size/2); + var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); + deltaXOld = @max(0, deltaXOld - size/2); + var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); + if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; + var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); + if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; + + const minZOld = olderPz-%maxZRenderDistanceOld & invMask; + const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; + const minZNew = lastPz-%maxZRenderDistanceNew & invMask; + const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; + + var zValues: [storageSize]i32 = undefined; + var zValuesLen: usize = 0; + if(minZNew -% minZOld > 0) { + var z = minZOld; + while(z != minZNew and z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZOld -% maxZNew > 0) { + var z = minZOld +% @max(0, maxZNew -% minZOld); + while(z != maxZOld): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = xIndex*storageSize + zIndex; + + const mapAtomic = &mapStorageLists[_lod][@intCast(index)]; + if(mapAtomic.load(.Acquire)) |map| { + mapAtomic.store(null, .Release); + map.decreaseRefCount(); + } + } + } + } +} + +fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition), mapRequests: *std.ArrayList(LightMap.MapFragmentPosition)) !void { + for(0..storageLists.len) |_lod| { + const lod: u5 = @intCast(_lod); + const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod; + const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod; + const size: u31 = chunk.chunkSize << lod; + const mask: i32 = size - 1; + const invMask: i32 = ~mask; + + std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize); + + const minX = lastPx-%maxRenderDistanceNew & invMask; + const maxX = lastPx+%maxRenderDistanceNew+%size & invMask; + var x = minX; + while(x != maxX): (x +%= size) { + const xIndex = @divExact(x, size) & storageMask; + var deltaXNew: i64 = @abs(x +% size/2 -% lastPx); + deltaXNew = @max(0, deltaXNew - size/2); + var deltaXOld: i64 = @abs(x +% size/2 -% olderPx); + deltaXOld = @max(0, deltaXOld - size/2); + const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew); + const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld); + + const minY = lastPy-%maxYRenderDistanceNew & invMask; + const maxY = lastPy+%maxYRenderDistanceNew+%size & invMask; + var y = minY; + while(y != maxY): (y +%= size) { + const yIndex = @divExact(y, size) & storageMask; + var deltaYOld: i64 = @abs(y +% size/2 -% olderPy); + deltaYOld = @max(0, deltaYOld - size/2); + var deltaYNew: i64 = @abs(y +% size/2 -% lastPy); + deltaYNew = @max(0, deltaYNew - size/2); + var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew); + if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2; + var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld); + if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2; + + const minZOld = olderPz-%maxZRenderDistanceOld & invMask; + const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask; + const minZNew = lastPz-%maxZRenderDistanceNew & invMask; + const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask; + + var zValues: [storageSize]i32 = undefined; + var zValuesLen: usize = 0; + if(minZOld -% minZNew > 0) { + var z = minZNew; + while(z != minZOld and z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZNew -% maxZOld > 0) { + var z = minZNew +% @max(0, maxZOld -% minZNew); + while(z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = (xIndex*storageSize + yIndex)*storageSize + zIndex; + const pos = chunk.ChunkPosition{.wx=x, .wy=y, .wz=z, .voxelSize=@as(u31, 1)< 0) { + var z = minZNew; + while(z != minZOld and z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + if(maxZNew -% maxZOld > 0) { + var z = minZNew +% @max(0, maxZOld -% minZNew); + while(z != maxZNew): (z +%= size) { + zValues[zValuesLen] = z; + zValuesLen += 1; + } + } + + for(zValues[0..zValuesLen]) |z| { + const zIndex = @divExact(z, size) & storageMask; + const index = xIndex*storageSize + zIndex; + const pos = LightMap.MapFragmentPosition{.wx=x, .wz=z, .voxelSize=@as(u31, 1)< b.distance) return .gt; + return .eq; + } + }; + + // TODO: Is there a way to combine this with minecraft's approach? + var searchList = std.PriorityQueue(OcclusionData, void, OcclusionData.compare).init(main.globalAllocator, {}); + defer searchList.deinit(); + { + var firstPos = chunk.ChunkPosition{ + .wx = @intFromFloat(@floor(playerPos[0])), + .wy = @intFromFloat(@floor(playerPos[1])), + .wz = @intFromFloat(@floor(playerPos[2])), + .voxelSize = 1, + }; + firstPos.wx &= ~@as(i32, chunk.chunkMask); + firstPos.wy &= ~@as(i32, chunk.chunkMask); + firstPos.wz &= ~@as(i32, chunk.chunkMask); + var lod: u3 = 0; + while(lod <= settings.highestLOD) : (lod += 1) { + const node = getNodeFromRenderThread(firstPos); + if(node.mesh.load(.Acquire) != null and node.mesh.load(.Acquire).?.finishedMeshing) { + node.lod = lod; + node.min = @splat(-1); + node.max = @splat(1); + node.active = true; + node.rendered = true; + try searchList.add(.{ + .node = node, + .distance = 0, + }); + break; + } + firstPos.wx &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.wy &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.wz &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.voxelSize *= 2; + } + } + var nodeList = std.ArrayList(*ChunkMeshNode).init(main.globalAllocator); + defer nodeList.deinit(); + const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix); + while(searchList.removeOrNull()) |data| { + try nodeList.append(data.node); + data.node.active = false; + const mesh = data.node.mesh.load(.Acquire).?; + std.debug.assert(mesh.finishedMeshing); + mesh.visibilityMask = 0xff; + const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz})) - playerPos; + const relPosFloat: Vec3f = @floatCast(relPos); + var isNeighborLod: [6]bool = .{false} ** 6; + for(chunk.Neighbors.iterable) |neighbor| continueNeighborLoop: { + const component = chunk.Neighbors.extractDirectionComponent(neighbor, relPos); + if(chunk.Neighbors.isPositive[neighbor] and component + @as(f64, @floatFromInt(chunk.chunkSize*mesh.pos.voxelSize)) <= 0) continue; + if(!chunk.Neighbors.isPositive[neighbor] and component >= 0) continue; + if(@reduce(.Or, @min(mesh.chunkBorders[neighbor].min, mesh.chunkBorders[neighbor].max) != mesh.chunkBorders[neighbor].min)) continue; // There was not a single block in the chunk. TODO: Find a better solution. + const minVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor].min*@as(Vec3i, @splat(mesh.pos.voxelSize))); + const maxVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor].max*@as(Vec3i, @splat(mesh.pos.voxelSize))); + var xyMin: Vec2f = .{10, 10}; + var xyMax: Vec2f = .{-10, -10}; + var numberOfNegatives: u8 = 0; + var corners: [5]Vec4f = undefined; + var curCorner: usize = 0; + for(0..2) |a| { + for(0..2) |b| { + + var cornerVector: Vec3f = undefined; + switch(chunk.Neighbors.vectorComponent[neighbor]) { + .x => { + cornerVector = @select(f32, @Vector(3, bool){true, a == 0, b == 0}, minVec, maxVec); + }, + .y => { + cornerVector = @select(f32, @Vector(3, bool){a == 0, true, b == 0}, minVec, maxVec); + }, + .z => { + cornerVector = @select(f32, @Vector(3, bool){a == 0, b == 0, true}, minVec, maxVec); + }, + } + corners[curCorner] = projRotMat.mulVec(vec.combine(relPosFloat + cornerVector, 1)); + if(corners[curCorner][3] < 0) { + numberOfNegatives += 1; + } + curCorner += 1; + } + } + switch(numberOfNegatives) { // Oh, so complicated. But this should only trigger very close to the player. + 4 => continue, + 0 => {}, + 1 => { + // Needs to duplicate the problematic corner and move it onto the projected plane. + var problematicOne: usize = 0; + for(0..curCorner) |i| { + if(corners[i][3] < 0) { + problematicOne = i; + break; + } + } + const problematicVector = corners[problematicOne]; + // The two neighbors of the quad: + const neighborA = corners[problematicOne ^ 1]; + const neighborB = corners[problematicOne ^ 2]; + // Move the problematic point towards the neighbor: + const one: Vec4f = @splat(1); + const weightA: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborA[3])); + var towardsA = neighborA*weightA + problematicVector*(one - weightA); + towardsA[3] = 0; // Prevent inaccuracies + const weightB: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborB[3])); + var towardsB = neighborB*weightB + problematicVector*(one - weightB); + towardsB[3] = 0; // Prevent inaccuracies + corners[problematicOne] = towardsA; + corners[curCorner] = towardsB; + curCorner += 1; + }, + 2 => { + // Needs to move the two problematic corners onto the projected plane. + var problematicOne: usize = undefined; + for(0..curCorner) |i| { + if(corners[i][3] < 0) { + problematicOne = i; + break; + } + } + const problematicVectorOne = corners[problematicOne]; + var problematicTwo: usize = undefined; + for(problematicOne+1..curCorner) |i| { + if(corners[i][3] < 0) { + problematicTwo = i; + break; + } + } + const problematicVectorTwo = corners[problematicTwo]; + + const commonDirection = problematicOne ^ problematicTwo; + const projectionDirection = commonDirection ^ 0b11; + // The respective neighbors: + const neighborOne = corners[problematicOne ^ projectionDirection]; + const neighborTwo = corners[problematicTwo ^ projectionDirection]; + // Move the problematic points towards the neighbor: + const one: Vec4f = @splat(1); + const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborOne[3])); + var towardsOne = neighborOne*weightOne + problematicVectorOne*(one - weightOne); + towardsOne[3] = 0; // Prevent inaccuracies + corners[problematicOne] = towardsOne; + + const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborTwo[3])); + var towardsTwo = neighborTwo*weightTwo + problematicVectorTwo*(one - weightTwo); + towardsTwo[3] = 0; // Prevent inaccuracies + corners[problematicTwo] = towardsTwo; + }, + 3 => { + // Throw away the far problematic vector, move the other two onto the projection plane. + var neighborIndex: usize = undefined; + for(0..curCorner) |i| { + if(corners[i][3] >= 0) { + neighborIndex = i; + break; + } + } + const neighborVector = corners[neighborIndex]; + const problematicVectorOne = corners[neighborIndex ^ 1]; + const problematicVectorTwo = corners[neighborIndex ^ 2]; + // Move the problematic points towards the neighbor: + const one: Vec4f = @splat(1); + const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborVector[3])); + var towardsOne = neighborVector*weightOne + problematicVectorOne*(one - weightOne); + towardsOne[3] = 0; // Prevent inaccuracies + + const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborVector[3])); + var towardsTwo = neighborVector*weightTwo + problematicVectorTwo*(one - weightTwo); + towardsTwo[3] = 0; // Prevent inaccuracies + + corners[0] = neighborVector; + corners[1] = towardsOne; + corners[2] = towardsTwo; + curCorner = 3; + }, + else => unreachable, + } + + for(0..curCorner) |i| { + const projected = corners[i]; + const xy = vec.xy(projected)/@as(Vec2f, @splat(@max(0, projected[3]))); + xyMin = @min(xyMin, xy); + xyMax = @max(xyMax, xy); + } + const min = @max(xyMin, data.node.min); + const max = @min(xyMax, data.node.max); + if(@reduce(.Or, min >= max)) continue; // Nothing to render. + var neighborPos = chunk.ChunkPosition{ + .wx = mesh.pos.wx + chunk.Neighbors.relX[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .wy = mesh.pos.wy + chunk.Neighbors.relY[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .wz = mesh.pos.wz + chunk.Neighbors.relZ[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .voxelSize = mesh.pos.voxelSize, + }; + var lod: u3 = data.node.lod; + while(lod <= settings.highestLOD) : (lod += 1) { + defer { + neighborPos.wx &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.wy &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.wz &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.voxelSize *= 2; + } + const node = getNodeFromRenderThread(neighborPos); + if(node.mesh.load(.Acquire)) |neighborMesh| { + if(!neighborMesh.finishedMeshing) continue; + // Ensure that there are no high-to-low lod transitions, which would produce cracks. + if(lod == data.node.lod and lod != settings.highestLOD and !node.rendered) { + var isValid: bool = true; + const relPos2: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{neighborPos.wx, neighborPos.wy, neighborPos.wz})) - playerPos; + for(chunk.Neighbors.iterable) |neighbor2| { + const component2 = chunk.Neighbors.extractDirectionComponent(neighbor2, relPos2); + if(chunk.Neighbors.isPositive[neighbor2] and component2 + @as(f64, @floatFromInt(chunk.chunkSize*neighborMesh.pos.voxelSize)) >= 0) continue; + if(!chunk.Neighbors.isPositive[neighbor2] and component2 <= 0) continue; + { // Check the chunk of same lod: + const neighborPos2 = chunk.ChunkPosition{ + .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .voxelSize = neighborPos.voxelSize, + }; + const node2 = getNodeFromRenderThread(neighborPos2); + if(node2.rendered) { + continue; + } + } + { // Check the chunk of higher lod + const neighborPos2 = chunk.ChunkPosition{ + .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .voxelSize = neighborPos.voxelSize << 1, + }; + const node2 = getNodeFromRenderThread(neighborPos2); + if(node2.rendered) { + isValid = false; + break; + } + } + } + if(!isValid) { + isNeighborLod[neighbor] = true; + continue; + } + } + if(lod != data.node.lod) { + isNeighborLod[neighbor] = true; + } + if(node.active) { + node.min = @min(node.min, min); + node.max = @max(node.max, max); + } else { + node.lod = lod; + node.min = min; + node.max = max; + node.active = true; + try searchList.add(.{ + .node = node, + .distance = neighborMesh.pos.getMaxDistanceSquared(playerPos), + }); + node.rendered = true; + } + break :continueNeighborLoop; + } + } + } + try mesh.changeLodBorders(isNeighborLod); + } + for(nodeList.items) |node| { + node.rendered = false; + const mesh = node.mesh.load(.Acquire).?; + if(mesh.pos.voxelSize != @as(u31, 1) << settings.highestLOD) { + const parent = getNodeFromRenderThread(.{.wx=mesh.pos.wx, .wy=mesh.pos.wy, .wz=mesh.pos.wz, .voxelSize=mesh.pos.voxelSize << 1}); + if(parent.mesh.load(.Acquire)) |parentMesh| { + const sizeShift = chunk.chunkShift + @ctz(mesh.pos.voxelSize); + const octantIndex: u3 = @intCast((mesh.pos.wx>>sizeShift & 1) | (mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.pos.wz>>sizeShift & 1)<<2); + parentMesh.visibilityMask &= ~(@as(u8, 1) << octantIndex); + } + } + mutex.lock(); + if(mesh.needsMeshUpdate) { + try mesh.uploadData(); + mesh.needsMeshUpdate = false; + } + mutex.unlock(); + // Remove empty meshes. + if(!mesh.isEmpty()) { + try meshList.append(mesh); + } + } + + return meshList.items; +} + +pub fn updateMeshes(targetTime: i64) !void { + { // First of all process all the block updates: + blockUpdateMutex.lock(); + defer blockUpdateMutex.unlock(); + for(blockUpdateList.items) |blockUpdate| { + const pos = chunk.ChunkPosition{.wx=blockUpdate.x, .wy=blockUpdate.y, .wz=blockUpdate.z, .voxelSize=1}; + const node = getNodeFromRenderThread(pos); + if(node.mesh.load(.Acquire)) |mesh| { + try mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock); + } // TODO: It seems like we simply ignore the block update if we don't have the mesh yet. + } + blockUpdateList.clearRetainingCapacity(); + } + mutex.lock(); + defer mutex.unlock(); + for(clearList.items) |mesh| { + mesh.deinit(); + main.globalAllocator.destroy(mesh); + } + clearList.clearRetainingCapacity(); + while (priorityMeshUpdateList.items.len != 0) { + const mesh = priorityMeshUpdateList.orderedRemove(0); + if(!mesh.needsMeshUpdate) { + mutex.unlock(); + defer mutex.lock(); + mesh.decreaseRefCount(); + continue; + } + mesh.needsMeshUpdate = false; + mutex.unlock(); + defer mutex.lock(); + mesh.decreaseRefCount(); + if(getNodeFromRenderThread(mesh.pos).mesh.load(.Acquire) != mesh) continue; // This mesh isn't used for rendering anymore. + try mesh.uploadData(); + if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. + } + while(mapUpdatableList.popOrNull()) |map| { + if(!isMapInRenderDistance(map.pos)) { + map.decreaseRefCount(); + } else { + if(getMapPieceLocation(map.pos.wx, map.pos.wz, map.pos.voxelSize).swap(map, .AcqRel)) |old| { + old.decreaseRefCount(); + } + } + } + while(updatableList.items.len != 0) { + // TODO: Find a faster solution than going through the entire list every frame. + var closestPriority: f32 = -std.math.floatMax(f32); + var closestIndex: usize = 0; + const playerPos = game.Player.getPosBlocking(); + { + var i: usize = 0; + while(i < updatableList.items.len) { + const mesh = updatableList.items[i]; + if(!isInRenderDistance(mesh.pos)) { + _ = updatableList.swapRemove(i); + mutex.unlock(); + defer mutex.lock(); + mesh.decreaseRefCount(); + continue; + } + const priority = mesh.pos.getPriority(playerPos); + if(priority > closestPriority) { + closestPriority = priority; + closestIndex = i; + } + i += 1; + } + if(updatableList.items.len == 0) break; + } + const mesh = updatableList.swapRemove(closestIndex); + mutex.unlock(); + defer mutex.lock(); + if(isInRenderDistance(mesh.pos)) { + const node = getNodeFromRenderThread(mesh.pos); + mesh.finishedMeshing = true; + try mesh.uploadData(); + if(node.mesh.swap(mesh, .AcqRel)) |oldMesh| { + oldMesh.decreaseRefCount(); + } + } else { + mesh.decreaseRefCount(); + } + if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. + } +} + +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 addToUpdateListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) !void { + std.debug.assert(mesh.refCount.load(.Monotonic) != 0); + mutex.lock(); + defer mutex.unlock(); + if(mesh.finishedMeshing) { + try priorityMeshUpdateList.append(mesh); + mesh.needsMeshUpdate = true; + } else { + mutex.unlock(); + defer mutex.lock(); + mesh.decreaseRefCount(); + } +} + +pub fn addMeshToStorage(mesh: *chunk_meshing.ChunkMesh) !void { + mutex.lock(); + defer mutex.unlock(); + if(isInRenderDistance(mesh.pos)) { + const node = getNodeFromRenderThread(mesh.pos); + if(node.mesh.cmpxchgStrong(null, mesh, .AcqRel, .Monotonic) != null) { + return error.AlreadyStored; + } else { + mesh.increaseRefCount(); + } + } +} + +pub const MeshGenerationTask = struct { + mesh: *chunk.Chunk, + + pub const vtable = utils.ThreadPool.VTable{ + .getPriority = @ptrCast(&getPriority), + .isStillNeeded = @ptrCast(&isStillNeeded), + .run = @ptrCast(&run), + .clean = @ptrCast(&clean), + }; + + pub fn schedule(mesh: *chunk.Chunk) !void { + const task = try main.globalAllocator.create(MeshGenerationTask); + task.* = MeshGenerationTask { + .mesh = mesh, + }; + try main.threadPool.addTask(task, &vtable); + } + + pub fn getPriority(self: *MeshGenerationTask) f32 { + return self.mesh.pos.getPriority(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time. + } + + pub fn isStillNeeded(self: *MeshGenerationTask) bool { + const distanceSqr = self.mesh.pos.getMinDistanceSquared(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time. + var maxRenderDistance = settings.renderDistance*chunk.chunkSize*self.mesh.pos.voxelSize; + maxRenderDistance += 2*self.mesh.pos.voxelSize*chunk.chunkSize; + return distanceSqr < @as(f64, @floatFromInt(maxRenderDistance*maxRenderDistance)); + } + + pub fn run(self: *MeshGenerationTask) Allocator.Error!void { + const pos = self.mesh.pos; + const mesh = try main.globalAllocator.create(chunk_meshing.ChunkMesh); + try mesh.init(pos, self.mesh); + mesh.regenerateMainMesh() catch |err| { + switch(err) { + error.AlreadyStored => { + mesh.decreaseRefCount(); + main.globalAllocator.destroy(self); + return; + }, + else => |_err| { + return _err; + } + } + }; + mutex.lock(); + defer mutex.unlock(); + updatableList.append(mesh) catch |err| { + std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)}); + if(@errorReturnTrace()) |trace| { + std.log.err("Trace: {}", .{trace}); + } + main.globalAllocator.destroy(self.mesh); + }; + main.globalAllocator.destroy(self); + } + + pub fn clean(self: *MeshGenerationTask) void { + main.globalAllocator.destroy(self.mesh); + main.globalAllocator.destroy(self); + } +}; + +pub fn updateBlock(x: i32, y: i32, z: i32, newBlock: blocks.Block) !void { + blockUpdateMutex.lock(); + try blockUpdateList.append(BlockUpdate{.x=x, .y=y, .z=z, .newBlock=newBlock}); + defer blockUpdateMutex.unlock(); +} + +pub fn updateChunkMesh(mesh: *chunk.Chunk) !void { + try MeshGenerationTask.schedule(mesh); +} + +pub fn updateLightMap(map: *LightMap.LightMapFragment) !void { + mutex.lock(); + defer mutex.unlock(); + try mapUpdatableList.append(map); +} \ No newline at end of file