From dbc416332aaa19e9895992281c704c61723510d3 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Thu, 29 Sep 2022 12:15:47 +0200 Subject: [PATCH] Add Mesh creation for voxelSize=1 chunks. My goal is to combine all meshes in one algorithm to simplify things from the java version. --- build.zig | 2 +- src/chunk.zig | 253 +++++++++++++++++++++++++++-------------------- src/graphics.zig | 2 +- src/network.zig | 60 +++++------ src/renderer.zig | 146 ++++++++++++++++++++++----- src/settings.zig | 5 +- src/utils.zig | 38 ++++--- 7 files changed, 322 insertions(+), 184 deletions(-) diff --git a/build.zig b/build.zig index f03b41c7..243f34cb 100644 --- a/build.zig +++ b/build.zig @@ -12,7 +12,7 @@ pub fn build(b: *std.build.Builder) void { const mode = b.standardReleaseOptions(); const exe = b.addExecutable("Cubyzig", "src/main.zig"); - exe.addIncludeDir("include"); + exe.addIncludePath("include"); exe.linkLibC(); { // compile glfw from source: if(target.getOsTag() == .windows) { diff --git a/src/chunk.zig b/src/chunk.zig index bf7e5e9e..1dad32f0 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -9,6 +9,7 @@ const c = graphics.c; const Shader = graphics.Shader; const SSBO = graphics.SSBO; const main = @import("main.zig"); +const renderer = @import("renderer.zig"); const vec = @import("vec.zig"); const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; @@ -40,11 +41,11 @@ pub const Neighbors = struct { /// Directions → Index pub const dirNegZ: u32 = 5; /// Index to relative position - pub const relX = [_]i32 {0, 0, 1, -1, 0, 0}; + pub const relX = [_]ChunkCoordinate {0, 0, 1, -1, 0, 0}; /// Index to relative position - pub const relY = [_]i32 {1, -1, 0, 0, 0, 0}; + pub const relY = [_]ChunkCoordinate {1, -1, 0, 0, 0, 0}; /// Index to relative position - pub const relZ = [_]i32 {0, 0, 0, 0, 1, -1}; + pub const relZ = [_]ChunkCoordinate {0, 0, 0, 0, 1, -1}; /// Index to bitMask for bitmap direction data pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20}; /// To iterate over all neighbors easily @@ -79,11 +80,6 @@ pub const ChunkPosition = struct { // public int hashCode() { // int shift = Math.min(Integer.numberOfTrailingZeros(wx), Math.min(Integer.numberOfTrailingZeros(wy), Integer.numberOfTrailingZeros(wz))); // return (((wx >> shift) * 31 + (wy >> shift)) * 31 + (wz >> shift)) * 31 + voxelSize; -// } -// TODO: -// public float getPriority(Player source) { -// int halfWidth = voxelSize * Chunk.chunkSize / 2; -// return -(float) source.getPosition().distance(wx + halfWidth, wy + halfWidth, wz + halfWidth) / voxelSize; // } pub fn getMinDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 { @@ -113,17 +109,15 @@ pub const Chunk = struct { widthShift: u5, mutex: std.Thread.Mutex, - pub fn init(self: *Chunk, wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) void { - std.debug.assert((voxelSize - 1 & voxelSize) == 0); - std.debug.assert(@mod(wx, voxelSize) == 0 and @mod(wy, voxelSize) == 0 and @mod(wz, voxelSize) == 0); - const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, voxelSize)); + pub fn init(self: *Chunk, pos: ChunkPosition) void { + std.debug.assert((pos.voxelSize - 1 & pos.voxelSize) == 0); + std.debug.assert(@mod(pos.wx, pos.voxelSize) == 0 and @mod(pos.wy, pos.voxelSize) == 0 and @mod(pos.wz, pos.voxelSize) == 0); + const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, pos.voxelSize)); self.* = Chunk { - .pos = ChunkPosition { - .wx = wx, .wy = wy, .wz = wz, .voxelSize = voxelSize - }, - .width = voxelSize*chunkSize, + .pos = pos, + .width = pos.voxelSize*chunkSize, .voxelSizeShift = voxelSizeShift, - .voxelSizeMask = voxelSize - 1, + .voxelSizeMask = pos.voxelSize - 1, .widthShift = voxelSizeShift + chunkShift, .mutex = std.Thread.Mutex{}, }; @@ -208,10 +202,10 @@ pub const Chunk = struct { /// Gets a block if it is inside this chunk. /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. - pub fn getBlock(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) Block { - x >>= self.voxelSizeShift; - y >>= self.voxelSizeShift; - z >>= self.voxelSizeShift; + pub fn getBlock(self: *const Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block { + var x = _x >> self.voxelSizeShift; + var y = _y >> self.voxelSizeShift; + var z = _z >> self.voxelSizeShift; var index = getIndex(x, y, z); return self.blocks[index]; } @@ -535,8 +529,6 @@ pub const meshing = struct { @"fog.activ": c_int, @"fog.color": c_int, @"fog.density": c_int, - lowerBounds: c_int, - upperBounds: c_int, texture_sampler: c_int, emissionSampler: c_int, @"waterFog.activ": c_int, @@ -583,7 +575,7 @@ pub const meshing = struct { c.glUniform3fv(uniforms.@"fog.color", 1, @ptrCast([*c]f32, &game.fog.color)); c.glUniform1f(uniforms.@"fog.density", game.fog.density); - c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]f32, &projMatrix)); + c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &projMatrix)); c.glUniform1i(uniforms.texture_sampler, 0); c.glUniform1i(uniforms.emissionSampler, 1); @@ -594,117 +586,160 @@ pub const meshing = struct { c.glUniform1i(uniforms.time, @bitCast(i32, time)); - c.glUniform3f(uniforms.lowerBounds, -std.math.inf_f32, -std.math.inf_f32, -std.math.inf_f32); - c.glUniform3f(uniforms.upperBounds, std.math.inf_f32, std.math.inf_f32, std.math.inf_f32); - - c.glBindVertexArray(vao); - } - - pub fn bindShaderForReplacement() void { - shader.bind(); c.glBindVertexArray(vao); } pub const ChunkMesh = struct { pos: ChunkPosition, size: ChunkCoordinate, - replacement: ?*ChunkMesh, + chunk: ?*Chunk, + faces: std.ArrayList(u32), faceData: SSBO, - vertexCount: c_int = 0, + coreCount: u31 = 0, + neighborStart: [7]u31 = [_]u31{0} ** 7, + vertexCount: u31 = 0, generated: bool = false, + mutex: std.Thread.Mutex = std.Thread.Mutex{}, - pub fn init(pos: ChunkPosition, replacement: ?*ChunkMesh) ChunkMesh { + pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh { return ChunkMesh{ .pos = pos, .size = chunkSize*pos.voxelSize, - .replacement = replacement, + .faces = std.ArrayList(u32).init(allocator), + .chunk = null, .faceData = SSBO.init(), }; } - pub fn deinit(self: ChunkMesh) void { + pub fn deinit(self: *ChunkMesh) void { self.faceData.deinit(); + self.faces.deinit(); + if(self.chunk) |ch| { + renderer.RenderStructure.allocator.destroy(ch); + } } - pub fn regenerateMesh(self: *ChunkMesh, visDat: *ChunkVisibilityData) !void { - self.generated = true; - - faces.clearRetainingCapacity(); - try faces.append(visDat.pos.voxelSize); - - for(visDat.visibles.items) |visible| { - const block = visible.block; - const x = visible.x; - const y = visible.y; - const z = visible.z; - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegX] != 0) { - const normal: u32 = 0; - const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegX] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); - } - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosX] != 0) { - const normal: u32 = 1; - const position: u32 = @as(u32, x+1) | @as(u32, y) << 6 | @as(u32, z) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosX] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); - } - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirDown] != 0) { - const normal: u32 = 4; - const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirDown] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); - } - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirUp] != 0) { - const normal: u32 = 5; - const position: u32 = @as(u32, x) | @as(u32, y+1) << 6 | @as(u32, z) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirUp] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); - } - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegZ] != 0) { - const normal: u32 = 2; - const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegZ] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); - } - if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosZ] != 0) { - const normal: u32 = 3; - const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z+1) << 12; - const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosZ] | (normal << 24); - try faces.append(position); - try faces.append(textureNormal); + pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. + self.faces.clearRetainingCapacity(); + try self.faces.append(chunk.pos.voxelSize); + 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 = (&chunk.blocks)[getIndex(x, y, z)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. + 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 = (&chunk.blocks)[getIndex(x2, y2, z2)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. + var isVisible = neighborBlock.typ == 0; // TODO: Transparency + if(isVisible) { + const normal: u32 = i; + const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10; + const textureNormal = blocks.meshes.textureIndices(block)[i] | normal<<24; + try self.faces.append(position); + try self.faces.append(textureNormal); + } + } + } } } - self.vertexCount = @intCast(c_int, 6*(faces.items.len-1)/2); - self.faceData.bufferData(u32, faces.items); + if(self.chunk) |oldChunk| { + renderer.RenderStructure.allocator.destroy(oldChunk); + } + self.chunk = chunk; + self.coreCount = @intCast(u31, self.faces.items.len); + self.neighborStart = [_]u31{self.coreCount} ** 7; + } + + pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. + if(self.chunk == null) return; // In the mean-time the mesh was discarded and recreated and all the data was lost. + self.generated = true; + self.faces.shrinkRetainingCapacity(self.coreCount); + for(Neighbors.iterable) |neighbor| { + self.neighborStart[neighbor] = @intCast(u31, self.faces.items.len); + var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, neighbor); + if(nullNeighborMesh) |neighborMesh| { + std.debug.assert(neighborMesh != self); + neighborMesh.mutex.lock(); + defer neighborMesh.mutex.unlock(); + if(neighborMesh.generated) { + var additionalNeighborFaces = std.ArrayList(u32).init(main.threadAllocator); + defer additionalNeighborFaces.deinit(); + var x3: u8 = if(neighbor & 1 == 0) @intCast(u8, chunkMask) else 0; + var x1: u8 = 0; + while(x1 < chunkSize): (x1 += 1) { + var x2: u8 = 0; + while(x2 < chunkSize): (x2 += 1) { + var x: u8 = undefined; + var y: u8 = undefined; + var z: u8 = 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; + } + var otherX = @intCast(u8, x+%Neighbors.relX[neighbor] & chunkMask); + var otherY = @intCast(u8, y+%Neighbors.relY[neighbor] & chunkMask); + var otherZ = @intCast(u8, z+%Neighbors.relZ[neighbor] & chunkMask); + var block = self.chunk.?.blocks[getIndex(x, y, z)]; + var otherBlock = neighborMesh.chunk.?.blocks[getIndex(otherX, otherY, otherZ)]; + if(otherBlock.typ == 0 and block.typ != 0) { // TODO: Transparency + const normal: u32 = neighbor; + const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10; + const textureNormal = blocks.meshes.textureIndices(block)[neighbor] | normal<<24; + try self.faces.append(position); + try self.faces.append(textureNormal); + } + if(block.typ == 0 and otherBlock.typ != 0) { // TODO: Transparency + const normal: u32 = neighbor ^ 1; + const position: u32 = @as(u32, otherX) | @as(u32, otherY)<<5 | @as(u32, otherZ)<<10; + const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor] | normal<<24; + try additionalNeighborFaces.append(position); + try additionalNeighborFaces.append(textureNormal); + } + } + } + var rangeStart = neighborMesh.neighborStart[neighbor ^ 1]; + var rangeEnd = neighborMesh.neighborStart[(neighbor ^ 1)+1]; + try neighborMesh.faces.replaceRange(rangeStart, rangeEnd - rangeStart, additionalNeighborFaces.items); + for(neighborMesh.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| { + neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @intCast(u31, additionalNeighborFaces.items.len); + } + neighborMesh.vertexCount = @intCast(u31, 6*(neighborMesh.faces.items.len-1)/2); + neighborMesh.faceData.bufferData(u32, neighborMesh.faces.items); + } else { + // TODO: Resolution boundary. + } + } else { + // TODO: Resolution boundary. + } + } + self.neighborStart[6] = @intCast(u31, self.faces.items.len); + self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2); + self.faceData.bufferData(u32, self.faces.items); } pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { if(!self.generated) { - if(self.replacement == null) return; - c.glUniform3f( - uniforms.lowerBounds, - @floatCast(f32, @intToFloat(f64, self.pos.wx) - playerPosition.x - 0.001), - @floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y - 0.001), - @floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z - 0.001) - ); - c.glUniform3f( - uniforms.upperBounds, - @floatCast(f32, @intToFloat(f64, self.pos.wx + self.size) - playerPosition.x + 0.001), - @floatCast(f32, @intToFloat(f64, self.pos.wy + self.size) - playerPosition.y + 0.001), - @floatCast(f32, @intToFloat(f64, self.pos.wz + self.size) - playerPosition.z + 0.001) - ); - - self.replacement.?.render(playerPosition); - - c.glUniform3f(uniforms.lowerBounds, -std.math.inf_f32, -std.math.inf_f32, -std.math.inf_f32); - c.glUniform3f(uniforms.upperBounds, std.math.inf_f32, std.math.inf_f32, std.math.inf_f32); return; } if(self.vertexCount == 0) return; diff --git a/src/graphics.zig b/src/graphics.zig index f123f749..1eef68b9 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -563,7 +563,7 @@ pub const TextureArray = struct { } }; -pub const Color = packed struct(u32) { +pub const Color = extern struct { r: u8, g: u8, b: u8, diff --git a/src/network.zig b/src/network.zig index cd4c0e26..d654d464 100644 --- a/src/network.zig +++ b/src/network.zig @@ -688,37 +688,39 @@ pub const Protocols = blk: { .wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]), .voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16])), }; - data = data[16..]; + const _inflatedData = try utils.Compression.inflate(main.threadAllocator, data[16..]); + data = _inflatedData; + defer main.threadAllocator.free(_inflatedData); if(pos.voxelSize == 1) { - // TODO: -// byte[] chunkData = ChunkIO.decompressChunk(data, offset, length); -// if(chunkData == null) -// return; -// VisibleChunk ch = new VisibleChunk(Cubyz.world, wx, wy, wz); -// ch.loadFromByteArray(chunkData, chunkData.length); -// ThreadPool.addTask(new ChunkLoadTask(ch)); - } else { - data = try utils.Compression.inflate(main.threadAllocator, data); - defer main.threadAllocator.free(data); - var size = @divExact(data.len, 8); - var x = data[0..size]; - var y = data[size..2*size]; - var z = data[2*size..3*size]; - var neighbors = data[3*size..4*size]; - var visibleBlocks = data[4*size..]; - var result = try renderer.RenderStructure.allocator.create(chunk.ChunkVisibilityData); - result.* = try chunk.ChunkVisibilityData.initEmpty(renderer.RenderStructure.allocator, pos, size); - for(x) |_, i| { - var block = result.visibles.addOneAssumeCapacity(); - block.x = x[i]; - block.y = y[i]; - block.z = z[i]; - block.neighbors = neighbors[i]; - var blockTypeAndData = std.mem.readIntBig(u32, visibleBlocks[4*i..][0..4]); - block.block.typ = @intCast(u16, blockTypeAndData & 0xffff); - block.block.data = @intCast(u16, blockTypeAndData >> 16); + var ch = try renderer.RenderStructure.allocator.create(chunk.Chunk); + ch.init(pos); + for(ch.blocks) |*block| { + var blockTypeAndData = std.mem.readIntBig(u32, data[0..4]); + block.typ = @intCast(u16, blockTypeAndData & 0xffff); + block.data = @intCast(u16, blockTypeAndData >> 16); + data = data[4..]; } - try renderer.RenderStructure.updateChunkMesh(result); + try renderer.RenderStructure.updateChunkMesh(ch); + } else { + //var size = @divExact(data.len, 8); + //var x = data[0..size]; + //var y = data[size..2*size]; + //var z = data[2*size..3*size]; + //var neighbors = data[3*size..4*size]; + //var visibleBlocks = data[4*size..]; + //var result = try renderer.RenderStructure.allocator.create(chunk.ChunkVisibilityData); + //result.* = try chunk.ChunkVisibilityData.initEmpty(renderer.RenderStructure.allocator, pos, size); + //for(x) |_, i| { + // var block = result.visibles.addOneAssumeCapacity(); + // block.x = x[i]; + // block.y = y[i]; + // block.z = z[i]; + // block.neighbors = neighbors[i]; + // var blockTypeAndData = std.mem.readIntBig(u32, visibleBlocks[4*i..][0..4]); + // block.block.typ = @intCast(u16, blockTypeAndData & 0xffff); + // block.block.data = @intCast(u16, blockTypeAndData >> 16); + //} + // TODO: try renderer.RenderStructure.updateChunkMesh(result); } } pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void { diff --git a/src/renderer.zig b/src/renderer.zig index 2ac6f0d2..745eec0a 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -16,6 +16,7 @@ const chunk = @import("chunk.zig"); const main = @import("main.zig"); const network = @import("network.zig"); const settings = @import("settings.zig"); +const utils = @import("utils.zig"); const Window = main.Window; /// The number of milliseconds after which no more chunk meshes are created. This allows the game to run smoother on movement. @@ -211,7 +212,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo var frustum = Frustum.init(Vec3f{.x=0, .y=0, .z=0}, game.camera.viewMatrix, settings.fov, zFarLOD, main.Window.width, main.Window.height); const time = @intCast(u32, std.time.milliTimestamp() & std.math.maxInt(u32)); - const waterFog = Fog{.active=true, .color=.{.x=0.0, .y=0.1, .z=0.2}, .density=0.1}; + var waterFog = Fog{.active=true, .color=.{.x=0.0, .y=0.1, .z=0.2}, .density=0.1}; // Update the uniforms. The uniforms are needed to render the replacement meshes. chunk.meshing.bindShaderAndUniforms(game.lodProjectionMatrix, ambientLight, time); @@ -235,7 +236,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo var meshes = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.threadAllocator); defer meshes.deinit(); - try RenderStructure.updateAndGetRenderChunks(game.world.?.conn, game.playerPos, 4, 2.0, frustum, &meshes); + try RenderStructure.updateAndGetRenderChunks(game.world.?.conn, game.playerPos, settings.renderDistance, settings.LODFactor, frustum, &meshes); // for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) { // if (mesh instanceof NormalChunkMesh) { // visibleChunks.add((NormalChunkMesh)mesh); @@ -433,7 +434,8 @@ pub const RenderStructure = struct { drawableChildren: u32, // How many children can be renderer. If this is 8 then there is no need to render this mesh. }; var storageLists: [settings.highestLOD + 1][]?*ChunkMeshNode = undefined; - var updatableList: std.ArrayList(*chunk.ChunkVisibilityData) = undefined; + var updatableList: std.ArrayList(chunk.ChunkPosition) = undefined; + var clearList: std.ArrayList(*ChunkMeshNode) = undefined; var lastRD: i32 = 0; var lastFactor: f32 = 0; var lastX: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1); @@ -448,7 +450,8 @@ pub const RenderStructure = struct { lastFactor = 0; gpa = std.heap.GeneralPurposeAllocator(.{}){}; allocator = gpa.allocator(); - updatableList = std.ArrayList(*chunk.ChunkVisibilityData).init(allocator); + updatableList = std.ArrayList(chunk.ChunkPosition).init(allocator); + clearList = std.ArrayList(*ChunkMeshNode).init(allocator); for(storageLists) |*storageList| { storageList.* = try allocator.alloc(?*ChunkMeshNode, 0); } @@ -464,11 +467,12 @@ pub const RenderStructure = struct { } allocator.free(storageList); } - for(updatableList.items) |updatable| { - updatable.visibles.deinit(); - allocator.destroy(updatable); - } updatableList.deinit(); + for(clearList.items) |chunkMesh| { + chunkMesh.mesh.deinit(); + allocator.destroy(chunkMesh); + } + clearList.deinit(); game.world.?.blockPalette.deinit(); if(gpa.deinit()) { @panic("Memory leak"); @@ -489,6 +493,15 @@ pub const RenderStructure = struct { return storageLists[lod][@intCast(usize, index)]; } + pub fn getNeighbor(_pos: chunk.ChunkPosition, neighbor: u3) ?*chunk.meshing.ChunkMesh { + var pos = _pos; + pos.wx += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relX[neighbor]; + pos.wy += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relY[neighbor]; + pos.wz += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relZ[neighbor]; + var node = _getNode(pos) orelse return null; + return &node.mesh; + } + pub fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32, frustum: Frustum, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void { if(lastRD != renderDistance and lastFactor != LODFactor) { // TODO: @@ -551,7 +564,7 @@ pub const RenderStructure = struct { _node.shouldBeRemoved = false; } else { node = try allocator.create(ChunkMeshNode); - node.?.mesh = chunk.meshing.ChunkMesh.init(pos, null); // TODO: Replacement mesh? + node.?.mesh = chunk.meshing.ChunkMesh.init(allocator, pos); node.?.shouldBeRemoved = true; // Might be removed in the next iteration. try meshRequests.append(pos); } @@ -590,8 +603,13 @@ pub const RenderStructure = struct { for(oldList) |nullMesh| { if(nullMesh) |mesh| { if(mesh.shouldBeRemoved) { - mesh.mesh.deinit(); - allocator.destroy(mesh); + if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing. + mesh.mesh.mutex.unlock(); + mesh.mesh.deinit(); + allocator.destroy(mesh); + } else { + try clearList.append(mesh); + } } else { mesh.shouldBeRemoved = true; } @@ -600,6 +618,19 @@ pub const RenderStructure = struct { allocator.free(oldList); } + var i: usize = 0; + while(i < clearList.items.len) { + const mesh = clearList.items[i]; + if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing. + mesh.mesh.mutex.unlock(); + mesh.mesh.deinit(); + allocator.destroy(mesh); + _ = clearList.swapRemove(i); + } else { + i += 1; + } + } + lastRD = renderDistance; lastFactor = LODFactor; // Make requests after updating the, to avoid concurrency issues and reduce the number of requests: @@ -610,27 +641,88 @@ pub const RenderStructure = struct { mutex.lock(); defer mutex.unlock(); while(updatableList.items.len != 0) { - const mesh = updatableList.pop(); - const nullNode = _getNode(mesh.pos); + const pos = updatableList.pop(); + mutex.unlock(); + defer mutex.lock(); + const nullNode = _getNode(pos); if(nullNode) |node| { - try node.mesh.regenerateMesh(mesh); + node.mesh.mutex.lock(); + defer node.mesh.mutex.unlock(); + try node.mesh.uploadDataAndFinishNeighbors(); } - mesh.visibles.deinit(); - allocator.destroy(mesh); if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. } } - pub fn updateChunkMesh(mesh: *chunk.ChunkVisibilityData) !void { - mutex.lock(); - defer mutex.unlock(); - try updatableList.append(mesh); + pub const MeshGenerationTask = struct { + mesh: *chunk.Chunk, + + pub const vtable = utils.ThreadPool.VTable{ + .getPriority = @ptrCast(*const fn(*anyopaque) f32, &getPriority), + .isStillNeeded = @ptrCast(*const fn(*anyopaque) bool, &isStillNeeded), + .run = @ptrCast(*const fn(*anyopaque) void, &run), + .clean = @ptrCast(*const fn(*anyopaque) void, &clean), + }; + + pub fn schedule(mesh: *chunk.Chunk) !void { + var task = try allocator.create(MeshGenerationTask); + task.* = MeshGenerationTask { + .mesh = mesh, + }; + try main.threadPool.addTask(task, &vtable); + } + + pub fn getPriority(self: *MeshGenerationTask) f32 { + return -@floatCast(f32, self.mesh.pos.getMinDistanceSquared(game.playerPos))/@intToFloat(f32, self.mesh.pos.voxelSize*self.mesh.pos.voxelSize); + } + + pub fn isStillNeeded(self: *MeshGenerationTask) bool { + var distanceSqr = self.mesh.pos.getMinDistanceSquared(game.playerPos); + var maxRenderDistance = settings.renderDistance*chunk.chunkSize*self.mesh.pos.voxelSize; + if(self.mesh.pos.voxelSize != 1) maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, maxRenderDistance)*settings.LODFactor)); + maxRenderDistance += 2*self.mesh.pos.voxelSize*chunk.chunkSize; + return distanceSqr < @intToFloat(f64, maxRenderDistance*maxRenderDistance); + } + + pub fn run(self: *MeshGenerationTask) void { + const pos = self.mesh.pos; + const nullNode = _getNode(pos); + if(nullNode) |node| { + { + node.mesh.mutex.lock(); + defer node.mesh.mutex.unlock(); + node.mesh.regenerateMainMesh(self.mesh) catch |err| { + std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)}); + if(@errorReturnTrace()) |trace| { + std.log.err("Trace: {}", .{trace}); + } + allocator.destroy(self.mesh); + allocator.destroy(self); + return; + }; + } + mutex.lock(); + defer mutex.unlock(); + updatableList.append(pos) catch |err| { + std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)}); + if(@errorReturnTrace()) |trace| { + std.log.err("Trace: {}", .{trace}); + } + allocator.destroy(self.mesh); + }; + } else { + allocator.destroy(self.mesh); + } + allocator.destroy(self); + } + + pub fn clean(self: *MeshGenerationTask) void { + allocator.destroy(self.mesh); + allocator.destroy(self); + } + }; + + pub fn updateChunkMesh(mesh: *chunk.Chunk) !void { + try MeshGenerationTask.schedule(mesh); } - // TODO: -// public void updateChunkMesh(VisibleChunk mesh) { -// OctTreeNode node = findNode(mesh); -// if (node != null) { -// ((NormalChunkMesh)node.mesh).updateChunk(mesh); -// } -// } }; diff --git a/src/settings.zig b/src/settings.zig index 5c297b71..472f38f3 100644 --- a/src/settings.zig +++ b/src/settings.zig @@ -11,4 +11,7 @@ pub const highestLOD: u5 = 5; pub var fov: f32 = 45; -pub var mouseSensitivity: f32 = 1; \ No newline at end of file +pub var mouseSensitivity: f32 = 1; + +pub var renderDistance: i32 = 4; +pub var LODFactor: f32 = 2.0; \ No newline at end of file diff --git a/src/utils.zig b/src/utils.zig index f26f3c21..9d27d2f1 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -118,6 +118,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { /// Moves an element from a given index down the heap, such that all children are always smaller than their parents. fn siftDown(self: *@This(), _i: usize) void { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. var i = _i; while(2*i + 2 < self.size) { var biggest = if(self.array[2*i + 1].biggerThan(self.array[2*i + 2])) 2*i + 1 else 2*i + 2; @@ -134,14 +135,15 @@ pub fn BlockingMaxHeap(comptime T: type) type { /// Moves an element from a given index up the heap, such that all children are always smaller than their parents. fn siftUp(self: *@This(), _i: usize) void { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. var i = _i; - var parentIndex = (i - 1)/2; - while(self.array[i].biggerThan(self.array[parentIndex]) and i > 0) { + while(i > 0) { + var parentIndex = (i - 1)/2; + if(!self.array[i].biggerThan(self.array[parentIndex])) break; var local = self.array[parentIndex]; self.array[parentIndex] = self.array[i]; self.array[i] = local; i = parentIndex; - parentIndex = (i - 1)/2; } } @@ -156,6 +158,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { /// Returns the i-th element in the heap. Useless for most applications. pub fn get(self: *@This(), i: usize) ?T { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. if(i >= self.size) return null; return self.array[i]; } @@ -176,6 +179,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { } fn removeIndex(self: *@This(), i: usize) void { + std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. self.size -= 1; self.array[i] = self.array[self.size]; self.siftDown(i); @@ -204,7 +208,7 @@ pub fn BlockingMaxHeap(comptime T: type) type { } fn increaseCapacity(self: *@This(), newCapacity: usize) !void { - self.array = self.allocator.realloc(self.array, newCapacity); + self.array = try self.allocator.realloc(self.array, newCapacity); } }; } @@ -213,7 +217,7 @@ pub const ThreadPool = struct { const Task = struct { cachedPriority: f32, self: *anyopaque, - vtable: *VTable, + vtable: *const VTable, fn biggerThan(self: Task, other: Task) bool { return self.cachedPriority > other.cachedPriority; @@ -278,16 +282,18 @@ pub const ThreadPool = struct { if(std.time.milliTimestamp() -% lastUpdate > refreshTime) { lastUpdate = std.time.milliTimestamp(); if(self.loadList.mutex.tryLock()) { - defer self.loadList.mutex.unlock(); - var i: u32 = 0; - while(i < self.loadList.size) { - var task = &self.loadList.array[i]; - if(!task.vtable.isStillNeeded(task.self)) { - self.loadList.removeIndex(i); - task.vtable.clean(task.self); - } else { - task.cachedPriority = task.vtable.getPriority(task.self); - i += 1; + { + defer self.loadList.mutex.unlock(); + var i: u32 = 0; + while(i < self.loadList.size) { + var task = self.loadList.array[i]; + if(!task.vtable.isStillNeeded(task.self)) { + self.loadList.removeIndex(i); + task.vtable.clean(task.self); + } else { + task.cachedPriority = task.vtable.getPriority(task.self); + i += 1; + } } } self.loadList.updatePriority(); @@ -296,7 +302,7 @@ pub const ThreadPool = struct { } } - pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *VTable) !void { + pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *const VTable) !void { try self.loadList.add(Task { .cachedPriority = vtable.getPriority(task), .vtable = vtable,