Add Mesh creation for voxelSize=1 chunks. My goal is to combine all meshes in one algorithm to simplify things from the java version.

This commit is contained in:
IntegratedQuantum 2022-09-29 12:15:47 +02:00
parent 76b9b118a1
commit dbc416332a
7 changed files with 322 additions and 184 deletions

View File

@ -12,7 +12,7 @@ pub fn build(b: *std.build.Builder) void {
const mode = b.standardReleaseOptions(); const mode = b.standardReleaseOptions();
const exe = b.addExecutable("Cubyzig", "src/main.zig"); const exe = b.addExecutable("Cubyzig", "src/main.zig");
exe.addIncludeDir("include"); exe.addIncludePath("include");
exe.linkLibC(); exe.linkLibC();
{ // compile glfw from source: { // compile glfw from source:
if(target.getOsTag() == .windows) { if(target.getOsTag() == .windows) {

View File

@ -9,6 +9,7 @@ const c = graphics.c;
const Shader = graphics.Shader; const Shader = graphics.Shader;
const SSBO = graphics.SSBO; const SSBO = graphics.SSBO;
const main = @import("main.zig"); const main = @import("main.zig");
const renderer = @import("renderer.zig");
const vec = @import("vec.zig"); const vec = @import("vec.zig");
const Vec3f = vec.Vec3f; const Vec3f = vec.Vec3f;
const Vec3d = vec.Vec3d; const Vec3d = vec.Vec3d;
@ -40,11 +41,11 @@ pub const Neighbors = struct {
/// Directions Index /// Directions Index
pub const dirNegZ: u32 = 5; pub const dirNegZ: u32 = 5;
/// Index to relative position /// 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 /// 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 /// 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 /// Index to bitMask for bitmap direction data
pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20}; pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20};
/// To iterate over all neighbors easily /// To iterate over all neighbors easily
@ -79,11 +80,6 @@ pub const ChunkPosition = struct {
// public int hashCode() { // public int hashCode() {
// int shift = Math.min(Integer.numberOfTrailingZeros(wx), Math.min(Integer.numberOfTrailingZeros(wy), Integer.numberOfTrailingZeros(wz))); // 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; // 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 { pub fn getMinDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 {
@ -113,17 +109,15 @@ pub const Chunk = struct {
widthShift: u5, widthShift: u5,
mutex: std.Thread.Mutex, mutex: std.Thread.Mutex,
pub fn init(self: *Chunk, wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) void { pub fn init(self: *Chunk, pos: ChunkPosition) void {
std.debug.assert((voxelSize - 1 & voxelSize) == 0); std.debug.assert((pos.voxelSize - 1 & pos.voxelSize) == 0);
std.debug.assert(@mod(wx, voxelSize) == 0 and @mod(wy, voxelSize) == 0 and @mod(wz, 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, voxelSize)); const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, pos.voxelSize));
self.* = Chunk { self.* = Chunk {
.pos = ChunkPosition { .pos = pos,
.wx = wx, .wy = wy, .wz = wz, .voxelSize = voxelSize .width = pos.voxelSize*chunkSize,
},
.width = voxelSize*chunkSize,
.voxelSizeShift = voxelSizeShift, .voxelSizeShift = voxelSizeShift,
.voxelSizeMask = voxelSize - 1, .voxelSizeMask = pos.voxelSize - 1,
.widthShift = voxelSizeShift + chunkShift, .widthShift = voxelSizeShift + chunkShift,
.mutex = std.Thread.Mutex{}, .mutex = std.Thread.Mutex{},
}; };
@ -208,10 +202,10 @@ pub const Chunk = struct {
/// Gets a block if it is inside this chunk. /// 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. /// 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 { pub fn getBlock(self: *const Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block {
x >>= self.voxelSizeShift; var x = _x >> self.voxelSizeShift;
y >>= self.voxelSizeShift; var y = _y >> self.voxelSizeShift;
z >>= self.voxelSizeShift; var z = _z >> self.voxelSizeShift;
var index = getIndex(x, y, z); var index = getIndex(x, y, z);
return self.blocks[index]; return self.blocks[index];
} }
@ -535,8 +529,6 @@ pub const meshing = struct {
@"fog.activ": c_int, @"fog.activ": c_int,
@"fog.color": c_int, @"fog.color": c_int,
@"fog.density": c_int, @"fog.density": c_int,
lowerBounds: c_int,
upperBounds: c_int,
texture_sampler: c_int, texture_sampler: c_int,
emissionSampler: c_int, emissionSampler: c_int,
@"waterFog.activ": 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.glUniform3fv(uniforms.@"fog.color", 1, @ptrCast([*c]f32, &game.fog.color));
c.glUniform1f(uniforms.@"fog.density", game.fog.density); 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.texture_sampler, 0);
c.glUniform1i(uniforms.emissionSampler, 1); c.glUniform1i(uniforms.emissionSampler, 1);
@ -594,117 +586,160 @@ pub const meshing = struct {
c.glUniform1i(uniforms.time, @bitCast(i32, time)); 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); c.glBindVertexArray(vao);
} }
pub const ChunkMesh = struct { pub const ChunkMesh = struct {
pos: ChunkPosition, pos: ChunkPosition,
size: ChunkCoordinate, size: ChunkCoordinate,
replacement: ?*ChunkMesh, chunk: ?*Chunk,
faces: std.ArrayList(u32),
faceData: SSBO, faceData: SSBO,
vertexCount: c_int = 0, coreCount: u31 = 0,
neighborStart: [7]u31 = [_]u31{0} ** 7,
vertexCount: u31 = 0,
generated: bool = false, 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{ return ChunkMesh{
.pos = pos, .pos = pos,
.size = chunkSize*pos.voxelSize, .size = chunkSize*pos.voxelSize,
.replacement = replacement, .faces = std.ArrayList(u32).init(allocator),
.chunk = null,
.faceData = SSBO.init(), .faceData = SSBO.init(),
}; };
} }
pub fn deinit(self: ChunkMesh) void { pub fn deinit(self: *ChunkMesh) void {
self.faceData.deinit(); self.faceData.deinit();
self.faces.deinit();
if(self.chunk) |ch| {
renderer.RenderStructure.allocator.destroy(ch);
}
} }
pub fn regenerateMesh(self: *ChunkMesh, visDat: *ChunkVisibilityData) !void { pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void {
self.generated = true; std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
self.faces.clearRetainingCapacity();
faces.clearRetainingCapacity(); try self.faces.append(chunk.pos.voxelSize);
try faces.append(visDat.pos.voxelSize); var n: u32 = 0;
var x: u8 = 0;
for(visDat.visibles.items) |visible| { while(x < chunkSize): (x += 1) {
const block = visible.block; var y: u8 = 0;
const x = visible.x; while(y < chunkSize): (y += 1) {
const y = visible.y; var z: u8 = 0;
const z = visible.z; while(z < chunkSize): (z += 1) {
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegX] != 0) { const block = (&chunk.blocks)[getIndex(x, y, z)]; // a little hack that increases speed 100×. TODO: check if this is *that* compiler bug.
const normal: u32 = 0; if(block.typ == 0) continue;
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; // Check all neighbors:
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegX] | (normal << 24); for(Neighbors.iterable) |i| {
try faces.append(position); n += 1;
try faces.append(textureNormal); const x2 = x + Neighbors.relX[i];
} const y2 = y + Neighbors.relY[i];
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosX] != 0) { const z2 = z + Neighbors.relZ[i];
const normal: u32 = 1; if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk.
const position: u32 = @as(u32, x+1) | @as(u32, y) << 6 | @as(u32, z) << 12; const neighborBlock = (&chunk.blocks)[getIndex(x2, y2, z2)]; // a little hack that increases speed 100×. TODO: check if this is *that* compiler bug.
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosX] | (normal << 24); var isVisible = neighborBlock.typ == 0; // TODO: Transparency
try faces.append(position); if(isVisible) {
try faces.append(textureNormal); const normal: u32 = i;
} const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10;
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirDown] != 0) { const textureNormal = blocks.meshes.textureIndices(block)[i] | normal<<24;
const normal: u32 = 4; try self.faces.append(position);
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; try self.faces.append(textureNormal);
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);
} }
} }
self.vertexCount = @intCast(c_int, 6*(faces.items.len-1)/2); if(self.chunk) |oldChunk| {
self.faceData.bufferData(u32, faces.items); 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 { pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void {
if(!self.generated) { 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; return;
} }
if(self.vertexCount == 0) return; if(self.vertexCount == 0) return;

View File

@ -563,7 +563,7 @@ pub const TextureArray = struct {
} }
}; };
pub const Color = packed struct(u32) { pub const Color = extern struct {
r: u8, r: u8,
g: u8, g: u8,
b: u8, b: u8,

View File

@ -688,37 +688,39 @@ pub const Protocols = blk: {
.wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]), .wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]),
.voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16])), .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) { if(pos.voxelSize == 1) {
// TODO: var ch = try renderer.RenderStructure.allocator.create(chunk.Chunk);
// byte[] chunkData = ChunkIO.decompressChunk(data, offset, length); ch.init(pos);
// if(chunkData == null) for(ch.blocks) |*block| {
// return; var blockTypeAndData = std.mem.readIntBig(u32, data[0..4]);
// VisibleChunk ch = new VisibleChunk(Cubyz.world, wx, wy, wz); block.typ = @intCast(u16, blockTypeAndData & 0xffff);
// ch.loadFromByteArray(chunkData, chunkData.length); block.data = @intCast(u16, blockTypeAndData >> 16);
// ThreadPool.addTask(new ChunkLoadTask(ch)); data = data[4..];
} 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);
} }
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 { pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void {

View File

@ -16,6 +16,7 @@ const chunk = @import("chunk.zig");
const main = @import("main.zig"); const main = @import("main.zig");
const network = @import("network.zig"); const network = @import("network.zig");
const settings = @import("settings.zig"); const settings = @import("settings.zig");
const utils = @import("utils.zig");
const Window = main.Window; 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. /// 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); 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 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. // Update the uniforms. The uniforms are needed to render the replacement meshes.
chunk.meshing.bindShaderAndUniforms(game.lodProjectionMatrix, ambientLight, time); 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); var meshes = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.threadAllocator);
defer meshes.deinit(); 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)) { // for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
// if (mesh instanceof NormalChunkMesh) { // if (mesh instanceof NormalChunkMesh) {
// visibleChunks.add((NormalChunkMesh)mesh); // 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. 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 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 lastRD: i32 = 0;
var lastFactor: f32 = 0; var lastFactor: f32 = 0;
var lastX: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1); var lastX: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
@ -448,7 +450,8 @@ pub const RenderStructure = struct {
lastFactor = 0; lastFactor = 0;
gpa = std.heap.GeneralPurposeAllocator(.{}){}; gpa = std.heap.GeneralPurposeAllocator(.{}){};
allocator = gpa.allocator(); 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| { for(storageLists) |*storageList| {
storageList.* = try allocator.alloc(?*ChunkMeshNode, 0); storageList.* = try allocator.alloc(?*ChunkMeshNode, 0);
} }
@ -464,11 +467,12 @@ pub const RenderStructure = struct {
} }
allocator.free(storageList); allocator.free(storageList);
} }
for(updatableList.items) |updatable| {
updatable.visibles.deinit();
allocator.destroy(updatable);
}
updatableList.deinit(); updatableList.deinit();
for(clearList.items) |chunkMesh| {
chunkMesh.mesh.deinit();
allocator.destroy(chunkMesh);
}
clearList.deinit();
game.world.?.blockPalette.deinit(); game.world.?.blockPalette.deinit();
if(gpa.deinit()) { if(gpa.deinit()) {
@panic("Memory leak"); @panic("Memory leak");
@ -489,6 +493,15 @@ pub const RenderStructure = struct {
return storageLists[lod][@intCast(usize, index)]; 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 { 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) { if(lastRD != renderDistance and lastFactor != LODFactor) {
// TODO: // TODO:
@ -551,7 +564,7 @@ pub const RenderStructure = struct {
_node.shouldBeRemoved = false; _node.shouldBeRemoved = false;
} else { } else {
node = try allocator.create(ChunkMeshNode); 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. node.?.shouldBeRemoved = true; // Might be removed in the next iteration.
try meshRequests.append(pos); try meshRequests.append(pos);
} }
@ -590,8 +603,13 @@ pub const RenderStructure = struct {
for(oldList) |nullMesh| { for(oldList) |nullMesh| {
if(nullMesh) |mesh| { if(nullMesh) |mesh| {
if(mesh.shouldBeRemoved) { if(mesh.shouldBeRemoved) {
mesh.mesh.deinit(); if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing.
allocator.destroy(mesh); mesh.mesh.mutex.unlock();
mesh.mesh.deinit();
allocator.destroy(mesh);
} else {
try clearList.append(mesh);
}
} else { } else {
mesh.shouldBeRemoved = true; mesh.shouldBeRemoved = true;
} }
@ -600,6 +618,19 @@ pub const RenderStructure = struct {
allocator.free(oldList); 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; lastRD = renderDistance;
lastFactor = LODFactor; lastFactor = LODFactor;
// Make requests after updating the, to avoid concurrency issues and reduce the number of requests: // 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(); mutex.lock();
defer mutex.unlock(); defer mutex.unlock();
while(updatableList.items.len != 0) { while(updatableList.items.len != 0) {
const mesh = updatableList.pop(); const pos = updatableList.pop();
const nullNode = _getNode(mesh.pos); mutex.unlock();
defer mutex.lock();
const nullNode = _getNode(pos);
if(nullNode) |node| { 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. if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
} }
} }
pub fn updateChunkMesh(mesh: *chunk.ChunkVisibilityData) !void { pub const MeshGenerationTask = struct {
mutex.lock(); mesh: *chunk.Chunk,
defer mutex.unlock();
try updatableList.append(mesh); 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);
// }
// }
}; };

View File

@ -11,4 +11,7 @@ pub const highestLOD: u5 = 5;
pub var fov: f32 = 45; pub var fov: f32 = 45;
pub var mouseSensitivity: f32 = 1; pub var mouseSensitivity: f32 = 1;
pub var renderDistance: i32 = 4;
pub var LODFactor: f32 = 2.0;

View File

@ -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. /// 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 { 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; var i = _i;
while(2*i + 2 < self.size) { 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; 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. /// 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 { 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 i = _i;
var parentIndex = (i - 1)/2; while(i > 0) {
while(self.array[i].biggerThan(self.array[parentIndex]) and i > 0) { var parentIndex = (i - 1)/2;
if(!self.array[i].biggerThan(self.array[parentIndex])) break;
var local = self.array[parentIndex]; var local = self.array[parentIndex];
self.array[parentIndex] = self.array[i]; self.array[parentIndex] = self.array[i];
self.array[i] = local; self.array[i] = local;
i = parentIndex; 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. /// Returns the i-th element in the heap. Useless for most applications.
pub fn get(self: *@This(), i: usize) ?T { 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; if(i >= self.size) return null;
return self.array[i]; return self.array[i];
} }
@ -176,6 +179,7 @@ pub fn BlockingMaxHeap(comptime T: type) type {
} }
fn removeIndex(self: *@This(), i: usize) void { 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.size -= 1;
self.array[i] = self.array[self.size]; self.array[i] = self.array[self.size];
self.siftDown(i); self.siftDown(i);
@ -204,7 +208,7 @@ pub fn BlockingMaxHeap(comptime T: type) type {
} }
fn increaseCapacity(self: *@This(), newCapacity: usize) !void { 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 { const Task = struct {
cachedPriority: f32, cachedPriority: f32,
self: *anyopaque, self: *anyopaque,
vtable: *VTable, vtable: *const VTable,
fn biggerThan(self: Task, other: Task) bool { fn biggerThan(self: Task, other: Task) bool {
return self.cachedPriority > other.cachedPriority; return self.cachedPriority > other.cachedPriority;
@ -278,16 +282,18 @@ pub const ThreadPool = struct {
if(std.time.milliTimestamp() -% lastUpdate > refreshTime) { if(std.time.milliTimestamp() -% lastUpdate > refreshTime) {
lastUpdate = std.time.milliTimestamp(); lastUpdate = std.time.milliTimestamp();
if(self.loadList.mutex.tryLock()) { if(self.loadList.mutex.tryLock()) {
defer self.loadList.mutex.unlock(); {
var i: u32 = 0; defer self.loadList.mutex.unlock();
while(i < self.loadList.size) { var i: u32 = 0;
var task = &self.loadList.array[i]; while(i < self.loadList.size) {
if(!task.vtable.isStillNeeded(task.self)) { var task = self.loadList.array[i];
self.loadList.removeIndex(i); if(!task.vtable.isStillNeeded(task.self)) {
task.vtable.clean(task.self); self.loadList.removeIndex(i);
} else { task.vtable.clean(task.self);
task.cachedPriority = task.vtable.getPriority(task.self); } else {
i += 1; task.cachedPriority = task.vtable.getPriority(task.self);
i += 1;
}
} }
} }
self.loadList.updatePriority(); 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 { try self.loadList.add(Task {
.cachedPriority = vtable.getPriority(task), .cachedPriority = vtable.getPriority(task),
.vtable = vtable, .vtable = vtable,