Cubyz/src/renderer/mesh_storage.zig
Krzysztof Wiśniewski 102a60ad30
Fix #1345 (#1422)
2025-05-10 10:37:15 +02:00

1082 lines
38 KiB
Zig

const std = @import("std");
const Atomic = std.atomic.Value;
const main = @import("main");
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 EventStatus = main.entity_data.EventStatus;
const chunk_meshing = @import("chunk_meshing.zig");
const ChunkMesh = chunk_meshing.ChunkMesh;
const ChunkMeshNode = struct {
mesh: ?*chunk_meshing.ChunkMesh = null,
active: bool = false,
rendered: bool = false,
finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing
finishedMeshingHigherResolution: u8 = 0, // Must be synced with finishedMeshing of the 8 higher resolution chunks.
pos: chunk.ChunkPosition = undefined,
isNeighborLod: [6]bool = @splat(false), // Must be synced with mesh.isNeighborLod
mutex: std.Thread.Mutex = .{},
};
const storageSize = 64;
const storageMask = storageSize - 1;
var storageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined;
var mapStorageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize]?*LightMap.LightMapFragment = undefined;
var meshList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
var priorityMeshUpdateList: main.utils.ConcurrentQueue(*chunk_meshing.ChunkMesh) = undefined;
pub var updatableList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
var mapUpdatableList: main.utils.ConcurrentQueue(*LightMap.LightMapFragment) = undefined;
var clearList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
var lastPx: i32 = 0;
var lastPy: i32 = 0;
var lastPz: i32 = 0;
var lastRD: u16 = 0;
var mutex: std.Thread.Mutex = .{};
pub const BlockUpdate = struct {
x: i32,
y: i32,
z: i32,
newBlock: blocks.Block,
pub fn init(pos: Vec3i, block: blocks.Block) BlockUpdate {
return .{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block};
}
};
var blockUpdateList: main.utils.ConcurrentQueue(BlockUpdate) = undefined;
var meshMemoryPool: main.heap.MemoryPool(chunk_meshing.ChunkMesh) = undefined;
pub fn init() void { // MARK: init()
lastRD = 0;
blockUpdateList = .init(main.globalAllocator, 16);
meshMemoryPool = .init(main.globalAllocator);
for(&storageLists) |*storageList| {
storageList.* = main.globalAllocator.create([storageSize*storageSize*storageSize]ChunkMeshNode);
for(storageList.*) |*val| {
val.* = .{};
}
}
for(&mapStorageLists) |*mapStorageList| {
mapStorageList.* = main.globalAllocator.create([storageSize*storageSize]?*LightMap.LightMapFragment);
@memset(mapStorageList.*, null);
}
priorityMeshUpdateList = .init(main.globalAllocator, 16);
mapUpdatableList = .init(main.globalAllocator, 16);
}
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);
for(storageLists) |storageList| {
main.globalAllocator.destroy(storageList);
}
for(mapStorageLists) |mapStorageList| {
main.globalAllocator.destroy(mapStorageList);
}
for(updatableList.items) |mesh| {
mesh.decreaseRefCount();
}
updatableList.clearAndFree();
while(mapUpdatableList.dequeue()) |map| {
map.decreaseRefCount();
}
mapUpdatableList.deinit();
while(priorityMeshUpdateList.dequeue()) |mesh| {
mesh.decreaseRefCount();
}
priorityMeshUpdateList.deinit();
blockUpdateList.deinit();
meshList.clearAndFree();
for(clearList.items) |mesh| {
mesh.deinit();
meshMemoryPool.destroy(mesh);
}
clearList.clearAndFree();
meshMemoryPool.deinit();
}
// MARK: getters
fn getNodePointer(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 finishedMeshingMask(x: bool, y: bool, z: bool) u8 {
return @as(u8, 1) << (@as(u3, @intFromBool(x))*4 + @as(u3, @intFromBool(y))*2 + @as(u3, @intFromBool(z)));
}
fn updateHigherLodNodeFinishedMeshing(pos_: chunk.ChunkPosition, finishedMeshing: bool) void {
const lod = std.math.log2_int(u31, pos_.voxelSize);
if(lod == settings.highestLod) return;
var pos = pos_;
pos.wx &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
pos.wy &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
pos.wz &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
pos.voxelSize *= 2;
const mask = finishedMeshingMask(pos.wx != pos_.wx, pos.wy != pos_.wy, pos.wz != pos_.wz);
const node = getNodePointer(pos);
if(finishedMeshing) {
node.finishedMeshingHigherResolution |= mask;
} else {
node.finishedMeshingHigherResolution &= ~mask;
}
}
fn getMapPiecePointer(x: i32, y: i32, voxelSize: u31) *?*LightMap.LightMapFragment {
const lod = std.math.log2_int(u31, voxelSize);
var xIndex = x >> lod + LightMap.LightMapFragment.mapShift;
var yIndex = y >> lod + LightMap.LightMapFragment.mapShift;
xIndex &= storageMask;
yIndex &= storageMask;
const index = xIndex*storageSize + yIndex;
return &(&mapStorageLists)[lod][@intCast(index)];
}
pub fn getLightMapPieceAndIncreaseRefCount(x: i32, y: i32, voxelSize: u31) ?*LightMap.LightMapFragment {
mutex.lock();
defer mutex.unlock();
const result: *LightMap.LightMapFragment = getMapPiecePointer(x, y, voxelSize).* orelse {
return null;
};
result.increaseRefCount();
return result;
}
pub fn getBlock(x: i32, y: i32, z: i32) ?blocks.Block {
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
node.mutex.lock();
defer node.mutex.unlock();
const mesh = node.mesh orelse return null;
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
return block;
}
pub fn triggerOnInteractBlock(x: i32, y: i32, z: i32) EventStatus {
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
node.mutex.lock();
defer node.mutex.unlock();
const mesh = node.mesh orelse return .ignored;
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
if(block.entityDataClass()) |class| {
return class.onInteract(.{x, y, z}, mesh.chunk);
}
// Event was not handled.
return .ignored;
}
pub fn getLight(wx: i32, wy: i32, wz: i32) ?[6]u8 {
const node = getNodePointer(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = 1});
node.mutex.lock();
defer node.mutex.unlock();
const mesh = node.mesh orelse return null;
const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const z = (wz >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
mesh.lightingData[0].lock.lockRead();
defer mesh.lightingData[0].lock.unlockRead();
mesh.lightingData[1].lock.lockRead();
defer mesh.lightingData[1].lock.unlockRead();
return mesh.lightingData[1].getValue(x, y, z) ++ mesh.lightingData[0].getValue(x, y, z);
}
pub fn getBlockFromAnyLod(x: i32, y: i32, z: i32) blocks.Block {
var lod: u5 = 0;
while(lod < settings.highestLod) : (lod += 1) {
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod});
node.mutex.lock();
defer node.mutex.unlock();
const mesh = node.mesh orelse continue;
const block = mesh.chunk.getBlock(x & chunk.chunkMask << lod, y & chunk.chunkMask << lod, z & chunk.chunkMask << lod);
return block;
}
return blocks.Block{.typ = 0, .data = 0};
}
pub fn getMeshAndIncreaseRefCount(pos: chunk.ChunkPosition) ?*chunk_meshing.ChunkMesh {
const lod = std.math.log2_int(u31, pos.voxelSize);
const mask = ~((@as(i32, 1) << lod + chunk.chunkShift) - 1);
const node = getNodePointer(pos);
node.mutex.lock();
const mesh = node.mesh orelse {
node.mutex.unlock();
return null;
};
mesh.increaseRefCount();
node.mutex.unlock();
if(pos.wx & mask != mesh.pos.wx or pos.wy & mask != mesh.pos.wy or pos.wz & mask != mesh.pos.wz) {
mesh.decreaseRefCount();
return null;
}
return mesh;
}
pub fn getMeshFromAnyLodAndIncreaseRefCount(wx: i32, wy: i32, wz: i32, voxelSize: u31) ?*chunk_meshing.ChunkMesh {
var lod: u5 = @ctz(voxelSize);
while(lod < settings.highestLod) : (lod += 1) {
const mesh = getMeshAndIncreaseRefCount(.{.wx = wx & ~chunk.chunkMask << lod, .wy = wy & ~chunk.chunkMask << lod, .wz = wz & ~chunk.chunkMask << lod, .voxelSize = @as(u31, 1) << lod});
return mesh orelse continue;
}
return null;
}
pub fn getNeighborAndIncreaseRefCount(_pos: chunk.ChunkPosition, resolution: u31, neighbor: chunk.Neighbor) ?*chunk_meshing.ChunkMesh {
var pos = _pos;
pos.wx +%= pos.voxelSize*chunk.chunkSize*neighbor.relX();
pos.wy +%= pos.voxelSize*chunk.chunkSize*neighbor.relY();
pos.wz +%= pos.voxelSize*chunk.chunkSize*neighbor.relZ();
pos.voxelSize = resolution;
return getMeshAndIncreaseRefCount(pos);
}
fn reduceRenderDistance(fullRenderDistance: i64, reduction: i64) i32 {
const reducedRenderDistanceSquare: f64 = @floatFromInt(fullRenderDistance*fullRenderDistance - reduction*reduction);
const reducedRenderDistance: i32 = @intFromFloat(@ceil(@sqrt(@max(0, reducedRenderDistanceSquare))));
return reducedRenderDistance;
}
fn isInRenderDistance(pos: chunk.ChunkPosition) bool { // MARK: isInRenderDistance()
const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize;
const size: u31 = chunk.chunkSize*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 < 0) return false;
if(pos.wx -% maxX >= 0) 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 < 0) return false;
if(pos.wy -% maxY >= 0) 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 < 0) return false;
if(pos.wz -% maxZ >= 0) 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 < 0) return false;
if(pos.wx -% maxX >= 0) return false;
var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx);
deltaX = @max(0, deltaX - size/2);
const maxYRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX);
if(maxYRenderDistance == 0) return false;
const minY = lastPy -% maxYRenderDistance & invMask;
const maxY = lastPy +% maxYRenderDistance +% size & invMask;
if(pos.wy -% minY < 0) return false;
if(pos.wy -% maxY >= 0) return false;
return true;
}
fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16) void { // MARK: freeOldMeshes()
for(0..settings.highestLod + 1) |_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)];
node.mutex.lock();
const oldMesh = node.mesh;
node.mesh = null;
node.mutex.unlock();
node.pos = undefined;
if(oldMesh) |mesh| {
node.finishedMeshing = false;
updateHigherLodNodeFinishedMeshing(mesh.pos, false);
mesh.decreaseRefCount();
}
node.isNeighborLod = @splat(false);
}
}
}
}
for(0..settings.highestLod + 1) |_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 maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
if(maxYRenderDistanceNew == 0) maxYRenderDistanceNew -= size/2;
var maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
if(maxYRenderDistanceOld == 0) maxYRenderDistanceOld -= size/2;
const minYOld = olderPy -% maxYRenderDistanceOld & invMask;
const maxYOld = olderPy +% maxYRenderDistanceOld +% size & invMask;
const minYNew = lastPy -% maxYRenderDistanceNew & invMask;
const maxYNew = lastPy +% maxYRenderDistanceNew +% size & invMask;
var yValues: [storageSize]i32 = undefined;
var yValuesLen: usize = 0;
if(minYNew -% minYOld > 0) {
var y = minYOld;
while(y != minYNew and y != maxYOld) : (y +%= size) {
yValues[yValuesLen] = y;
yValuesLen += 1;
}
}
if(maxYOld -% maxYNew > 0) {
var y = minYOld +% @max(0, maxYNew -% minYOld);
while(y != maxYOld) : (y +%= size) {
yValues[yValuesLen] = y;
yValuesLen += 1;
}
}
for(yValues[0..yValuesLen]) |y| {
const yIndex = @divExact(y, size) & storageMask;
const index = xIndex*storageSize + yIndex;
const mapPointer = &mapStorageLists[_lod][@intCast(index)];
mutex.lock();
const oldMap = mapPointer.*;
mapPointer.* = null;
mutex.unlock();
if(oldMap) |map| {
map.decreaseRefCount();
}
}
}
}
}
fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16, meshRequests: *main.List(chunk.ChunkPosition), mapRequests: *main.List(LightMap.MapFragmentPosition)) void { // MARK: createNewMeshes()
for(0..settings.highestLod + 1) |_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) << lod};
const node = &storageLists[_lod][@intCast(index)];
node.mutex.lock();
node.pos = pos;
if(node.mesh) |mesh| {
std.debug.assert(std.meta.eql(pos, mesh.pos));
} else {
meshRequests.append(pos);
}
node.mutex.unlock();
}
}
}
}
for(0..settings.highestLod + 1) |_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 = 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);
var maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
if(maxYRenderDistanceNew == 0) maxYRenderDistanceNew -= size/2;
var maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
if(maxYRenderDistanceOld == 0) maxYRenderDistanceOld -= size/2;
const minYOld = olderPy -% maxYRenderDistanceOld & invMask;
const maxYOld = olderPy +% maxYRenderDistanceOld +% size & invMask;
const minYNew = lastPy -% maxYRenderDistanceNew & invMask;
const maxYNew = lastPy +% maxYRenderDistanceNew +% size & invMask;
var yValues: [storageSize]i32 = undefined;
var yValuesLen: usize = 0;
if(minYOld -% minYNew > 0) {
var y = minYNew;
while(y != minYOld and y != maxYNew) : (y +%= size) {
yValues[yValuesLen] = y;
yValuesLen += 1;
}
}
if(maxYNew -% maxYOld > 0) {
var y = minYNew +% @max(0, maxYOld -% minYNew);
while(y != maxYNew) : (y +%= size) {
yValues[yValuesLen] = y;
yValuesLen += 1;
}
}
for(yValues[0..yValuesLen]) |y| {
const yIndex = @divExact(y, size) & storageMask;
const index = xIndex*storageSize + yIndex;
const pos = LightMap.MapFragmentPosition{.wx = x, .wy = y, .voxelSize = @as(u31, 1) << lod, .voxelSizeShift = lod};
const node = &mapStorageLists[_lod][@intCast(index)];
mutex.lock();
if(node.*) |map| {
std.debug.assert(std.meta.eql(pos, map.pos));
} else {
mapRequests.append(pos);
}
mutex.unlock();
}
}
}
}
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *const main.renderer.Frustum, playerPos: Vec3d, renderDistance: u16) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks()
meshList.clearRetainingCapacity();
const playerPosInt: Vec3i = @intFromFloat(@floor(playerPos));
var meshRequests = main.List(chunk.ChunkPosition).init(main.stackAllocator);
defer meshRequests.deinit();
var mapRequests = main.List(LightMap.MapFragmentPosition).init(main.stackAllocator);
defer mapRequests.deinit();
const olderPx = lastPx;
const olderPy = lastPy;
const olderPz = lastPz;
const olderRD = lastRD;
mutex.lock();
lastPx = @intFromFloat(playerPos[0]);
lastPy = @intFromFloat(playerPos[1]);
lastPz = @intFromFloat(playerPos[2]);
lastRD = renderDistance;
mutex.unlock();
freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests, &mapRequests);
// Make requests as soon as possible to reduce latency:
network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items);
network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items, .{lastPx, lastPy, lastPz}, lastRD);
// Finds all visible chunks and lod chunks using a breadth-first hierarchical search.
var searchList = main.utils.CircularBufferQueue(*ChunkMeshNode).init(main.stackAllocator, 1024);
defer searchList.deinit();
{
var firstPos = chunk.ChunkPosition{
.wx = @intFromFloat(@floor(playerPos[0])),
.wy = @intFromFloat(@floor(playerPos[1])),
.wz = @intFromFloat(@floor(playerPos[2])),
.voxelSize = 1,
};
const lod: u3 = settings.highestLod;
firstPos.wx &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
firstPos.wy &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
firstPos.wz &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
firstPos.voxelSize <<= lod;
const node = getNodePointer(firstPos);
const hasMesh = node.finishedMeshing;
if(hasMesh) {
node.active = true;
node.rendered = true;
searchList.enqueue(node);
}
}
var nodeList = main.List(*ChunkMeshNode).initCapacity(main.stackAllocator, 1024);
defer nodeList.deinit();
while(searchList.dequeue()) |node| {
std.debug.assert(node.finishedMeshing);
std.debug.assert(node.active);
if(!node.active) continue;
node.active = false;
const pos = node.pos;
const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{pos.wx, pos.wy, pos.wz})) - playerPos;
const relPosFloat: Vec3f = @floatCast(relPos);
if(pos.voxelSize == @as(i32, 1) << settings.highestLod) {
for(chunk.Neighbor.iterable) |neighbor| {
const component = neighbor.extractDirectionComponent(relPosFloat);
if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*pos.voxelSize)) <= 0) continue;
if(!neighbor.isPositive() and component >= 0) continue;
const neighborPos = chunk.ChunkPosition{
.wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize,
.wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize,
.wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize,
.voxelSize = pos.voxelSize,
};
const node2 = getNodePointer(neighborPos);
if(!node2.active and node2.finishedMeshing) {
if(!frustum.testAAB(relPosFloat + @as(Vec3f, @floatFromInt(Vec3i{neighbor.relX()*chunk.chunkSize*pos.voxelSize, neighbor.relY()*chunk.chunkSize*pos.voxelSize, neighbor.relZ()*chunk.chunkSize*pos.voxelSize})), @splat(@floatFromInt(chunk.chunkSize*pos.voxelSize))))
continue;
node2.active = true;
node2.rendered = true;
searchList.enqueue(node2);
}
}
}
if(node.finishedMeshingHigherResolution == 0xff) {
node.rendered = false;
const lowerLodBit: i32 = pos.voxelSize*chunk.chunkSize >> 1;
const startPos: chunk.ChunkPosition = .{
.wx = pos.wx | if((pos.wx | lowerLodBit) -% playerPosInt[0] > 0) lowerLodBit else 0,
.wy = pos.wy | if((pos.wy | lowerLodBit) -% playerPosInt[1] > 0) lowerLodBit else 0,
.wz = pos.wz | if((pos.wz | lowerLodBit) -% playerPosInt[2] > 0) lowerLodBit else 0,
.voxelSize = pos.voxelSize >> 1,
};
for(0..2) |dx| {
for(0..2) |dy| {
for(0..2) |dz| {
var nextPos = startPos;
if(dx == 1) nextPos.wx ^= lowerLodBit;
if(dy == 1) nextPos.wy ^= lowerLodBit;
if(dz == 1) nextPos.wz ^= lowerLodBit;
const node2 = getNodePointer(nextPos);
const relNextPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{nextPos.wx, nextPos.wy, nextPos.wz})) - playerPos;
if(!frustum.testAAB(@floatCast(relNextPos), @splat(@floatFromInt(chunk.chunkSize*nextPos.voxelSize))))
continue;
std.debug.assert(node2.finishedMeshing);
node2.active = true;
node2.rendered = true;
searchList.enqueue_back(node2);
}
}
}
} else {
nodeList.append(node);
}
}
for(nodeList.items) |node| {
const pos = node.pos;
var isNeighborLod: [6]bool = @splat(false);
if(pos.voxelSize != @as(i32, 1) << settings.highestLod) {
for(chunk.Neighbor.iterable) |neighbor| {
var neighborPos = chunk.ChunkPosition{
.wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize,
.wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize,
.wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize,
.voxelSize = pos.voxelSize,
};
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 node2 = getNodePointer(neighborPos);
isNeighborLod[neighbor.toInt()] = node2.finishedMeshingHigherResolution != 0xff;
}
}
if(!std.meta.eql(node.isNeighborLod, isNeighborLod)) {
const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
mesh.isNeighborLod = isNeighborLod;
node.isNeighborLod = isNeighborLod;
mesh.uploadData();
}
}
for(nodeList.items) |node| {
node.rendered = false;
if(!node.finishedMeshing) continue;
const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
node.mutex.lock();
if(mesh.needsMeshUpdate) {
mesh.uploadData();
mesh.needsMeshUpdate = false;
}
node.mutex.unlock();
// Remove empty meshes.
if(!mesh.isEmpty()) {
meshList.append(mesh);
}
}
return meshList.items;
}
pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()=
if(!blockUpdateList.empty()) batchUpdateBlocks();
mutex.lock();
defer mutex.unlock();
for(clearList.items) |mesh| {
mesh.deinit();
meshMemoryPool.destroy(mesh);
}
clearList.clearRetainingCapacity();
while(priorityMeshUpdateList.dequeue()) |mesh| {
if(!mesh.needsMeshUpdate) {
mutex.unlock();
defer mutex.lock();
mesh.decreaseRefCount();
continue;
}
mesh.needsMeshUpdate = false;
const node = getNodePointer(mesh.pos);
node.mutex.lock();
if(node.mesh != mesh) {
node.mutex.unlock();
mutex.unlock();
defer mutex.lock();
mesh.decreaseRefCount();
continue;
}
node.mutex.unlock();
mutex.unlock();
defer mutex.lock();
mesh.decreaseRefCount();
mesh.uploadData();
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
}
while(mapUpdatableList.dequeue()) |map| {
if(!isMapInRenderDistance(map.pos)) {
map.decreaseRefCount();
} else {
const mapPointer = getMapPiecePointer(map.pos.wx, map.pos.wy, map.pos.voxelSize);
if(mapPointer.*) |old| {
old.decreaseRefCount();
}
mapPointer.* = map;
}
}
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.getEyePosBlocking();
{
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 = getNodePointer(mesh.pos);
std.debug.assert(std.meta.eql(node.pos, mesh.pos));
node.finishedMeshing = true;
mesh.finishedMeshing = true;
updateHigherLodNodeFinishedMeshing(mesh.pos, true);
mesh.uploadData();
node.mutex.lock();
const oldMesh = node.mesh;
node.mesh = mesh;
node.mutex.unlock();
if(oldMesh) |_oldMesh| {
_oldMesh.decreaseRefCount();
}
} else {
mesh.decreaseRefCount();
}
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
}
}
fn batchUpdateBlocks() void {
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer lightRefreshList.deinit();
var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer regenerateMeshList.deinit();
// First of all process all the block updates:
while(blockUpdateList.dequeue()) |blockUpdate| {
const pos = chunk.ChunkPosition{.wx = blockUpdate.x, .wy = blockUpdate.y, .wz = blockUpdate.z, .voxelSize = 1};
if(getMeshAndIncreaseRefCount(pos)) |mesh| {
mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, &lightRefreshList, &regenerateMeshList);
mesh.decreaseRefCount();
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
}
for(regenerateMeshList.items) |mesh| {
mesh.generateMesh(&lightRefreshList);
}
{
for(lightRefreshList.items) |mesh| {
if(mesh.needsLightRefresh.load(.unordered)) {
mesh.scheduleLightRefreshAndDecreaseRefCount();
} else {
mesh.decreaseRefCount();
}
}
}
for(regenerateMeshList.items) |mesh| {
mesh.uploadData();
mesh.decreaseRefCount();
}
}
// MARK: adders
pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void {
std.debug.assert(mesh.refCount.load(.monotonic) == 0);
mutex.lock();
defer mutex.unlock();
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) {
priorityMeshUpdateList.enqueue(mesh);
mesh.needsMeshUpdate = true;
} else {
mutex.unlock();
defer mutex.lock();
mesh.decreaseRefCount();
}
}
pub fn addMeshToStorage(mesh: *chunk_meshing.ChunkMesh) error{AlreadyStored}!void {
mutex.lock();
defer mutex.unlock();
if(isInRenderDistance(mesh.pos)) {
const node = getNodePointer(mesh.pos);
node.mutex.lock();
defer node.mutex.unlock();
if(node.mesh != null) {
return error.AlreadyStored;
}
node.mesh = mesh;
node.finishedMeshing = mesh.finishedMeshing;
updateHigherLodNodeFinishedMeshing(mesh.pos, mesh.finishedMeshing);
mesh.increaseRefCount();
}
}
pub fn finishMesh(mesh: *chunk_meshing.ChunkMesh) void {
mutex.lock();
defer mutex.unlock();
mesh.increaseRefCount();
updatableList.append(mesh);
}
pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask
mesh: *chunk.Chunk,
pub const vtable = utils.ThreadPool.VTable{
.getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority),
.isStillNeeded = main.utils.castFunctionSelfToAnyopaque(isStillNeeded),
.run = main.utils.castFunctionSelfToAnyopaque(run),
.clean = main.utils.castFunctionSelfToAnyopaque(clean),
.taskType = .meshgenAndLighting,
};
pub fn schedule(mesh: *chunk.Chunk) void {
const task = main.globalAllocator.create(MeshGenerationTask);
task.* = MeshGenerationTask{
.mesh = mesh,
};
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(@intFromFloat(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 < maxRenderDistance*maxRenderDistance;
}
pub fn run(self: *MeshGenerationTask) void {
defer main.globalAllocator.destroy(self);
const pos = self.mesh.pos;
const mesh = meshMemoryPool.create();
mesh.init(pos, self.mesh);
defer mesh.decreaseRefCount();
mesh.generateLightingData() catch return;
}
pub fn clean(self: *MeshGenerationTask) void {
self.mesh.deinit();
main.globalAllocator.destroy(self);
}
};
// MARK: updaters
pub fn updateBlock(update: BlockUpdate) void {
blockUpdateList.enqueue(update);
}
pub fn updateChunkMesh(mesh: *chunk.Chunk) void {
MeshGenerationTask.schedule(mesh);
}
pub fn updateLightMap(map: *LightMap.LightMapFragment) void {
mapUpdatableList.enqueue(map);
}
// MARK: Block breaking animation
pub fn addBreakingAnimation(pos: Vec3i, breakingProgress: f32) void {
const animationFrame: usize = @intFromFloat(breakingProgress*@as(f32, @floatFromInt(main.blocks.meshes.blockBreakingTextures.items.len)));
const texture = main.blocks.meshes.blockBreakingTextures.items[animationFrame];
const block = getBlock(pos[0], pos[1], pos[2]) orelse return;
const model = main.blocks.meshes.model(block).model();
for(model.internalQuads) |quadIndex| {
addBreakingAnimationFace(pos, quadIndex, texture, null, block.transparent());
}
for(&model.neighborFacingQuads, 0..) |quads, n| {
for(quads) |quadIndex| {
addBreakingAnimationFace(pos, quadIndex, texture, @enumFromInt(n), block.transparent());
}
}
}
fn addBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, texture: u16, neighbor: ?chunk.Neighbor, isTransparent: bool) void {
const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask));
const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
defer mesh.decreaseRefCount();
mesh.mutex.lock();
defer mesh.mutex.unlock();
const lightIndex = blk: {
const meshData = if(isTransparent) &mesh.transparentMesh else &mesh.opaqueMesh;
meshData.lock.lockRead();
defer meshData.lock.unlockRead();
for(meshData.completeList.getEverything()) |face| {
if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) {
break :blk face.position.lightIndex;
}
}
// The face doesn't exist.
return;
};
mesh.blockBreakingFacesChanged = true;
mesh.blockBreakingFaces.append(.{
.position = .{
.x = @intCast(relPos[0]),
.y = @intCast(relPos[1]),
.z = @intCast(relPos[2]),
.isBackFace = false,
.lightIndex = lightIndex,
},
.blockAndQuad = .{
.texture = texture,
.quadIndex = quadIndex,
},
});
}
fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, neighbor: ?chunk.Neighbor) void {
const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask));
const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
defer mesh.decreaseRefCount();
for(mesh.blockBreakingFaces.items, 0..) |face, i| {
if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) {
_ = mesh.blockBreakingFaces.swapRemove(i);
mesh.blockBreakingFacesChanged = true;
break;
}
}
}
pub fn removeBreakingAnimation(pos: Vec3i) void {
const block = getBlock(pos[0], pos[1], pos[2]) orelse return;
const model = main.blocks.meshes.model(block).model();
for(model.internalQuads) |quadIndex| {
removeBreakingAnimationFace(pos, quadIndex, null);
}
for(&model.neighborFacingQuads, 0..) |quads, n| {
for(quads) |quadIndex| {
removeBreakingAnimationFace(pos, quadIndex, @enumFromInt(n));
}
}
}