mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-12 22:09:18 -04:00
Add sunlight.
This commit is contained in:
parent
dcb76a77a8
commit
3eb3e2b358
@ -992,6 +992,28 @@ pub const meshing = struct {
|
|||||||
for(self.lightingData[3..]) |*lightingData| {
|
for(self.lightingData[3..]) |*lightingData| {
|
||||||
try lightingData.propagateLights(lightEmittingBlocks.items, true);
|
try lightingData.propagateLights(lightEmittingBlocks.items, true);
|
||||||
}
|
}
|
||||||
|
sunLight: {
|
||||||
|
var sunStarters: [chunkSize*chunkSize][3]u8 = undefined;
|
||||||
|
var index: usize = 0;
|
||||||
|
const lightStartMap = renderer.RenderStructure.getLightMapPieceAndIncreaseRefCount(self.pos.wx, self.pos.wz, self.pos.voxelSize) orelse break :sunLight;
|
||||||
|
defer lightStartMap.decreaseRefCount();
|
||||||
|
x = 0;
|
||||||
|
while(x < chunkSize): (x += 1) {
|
||||||
|
var z: u8 = 0;
|
||||||
|
while(z < chunkSize): (z += 1) {
|
||||||
|
const startHeight: i32 = lightStartMap.getHeight(self.pos.wx + x*self.pos.voxelSize, self.pos.wz + z*self.pos.voxelSize);
|
||||||
|
const relHeight = startHeight -% self.pos.wy;
|
||||||
|
if(relHeight < chunkSize*self.pos.voxelSize) {
|
||||||
|
sunStarters[index] = .{x, chunkSize-1, z};
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(self.lightingData[0..3]) |*lightingData| {
|
||||||
|
try lightingData.propagateLights(sunStarters[0..index], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Sunlight propagation
|
// TODO: Sunlight propagation
|
||||||
try self.finishNeighbors(false);
|
try self.finishNeighbors(false);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,13 @@ const Channel = enum(u8) {
|
|||||||
.blue, .sun_blue => return 0,
|
.blue, .sun_blue => return 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isSun(self: Channel) bool {
|
||||||
|
switch(self) {
|
||||||
|
.sun_red, .sun_green, .sun_blue => return true,
|
||||||
|
.red, .green, .blue => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ChannelChunk = struct {
|
pub const ChannelChunk = struct {
|
||||||
@ -61,7 +68,9 @@ pub const ChannelChunk = struct {
|
|||||||
const ny = entry.y + chunk.Neighbors.relY[neighbor];
|
const ny = entry.y + chunk.Neighbors.relY[neighbor];
|
||||||
const nz = entry.z + chunk.Neighbors.relZ[neighbor];
|
const nz = entry.z + chunk.Neighbors.relZ[neighbor];
|
||||||
var result: Entry = .{.x = @intCast(nx & chunk.chunkMask), .y = @intCast(ny & chunk.chunkMask), .z = @intCast(nz & chunk.chunkMask), .value = entry.value};
|
var result: Entry = .{.x = @intCast(nx & chunk.chunkMask), .y = @intCast(ny & chunk.chunkMask), .z = @intCast(nz & chunk.chunkMask), .value = entry.value};
|
||||||
result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
|
if(!self.channel.isSun() or neighbor != chunk.Neighbors.dirDown or result.value != 255) {
|
||||||
|
result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
|
||||||
|
}
|
||||||
if(result.value == 0) continue;
|
if(result.value == 0) continue;
|
||||||
if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) {
|
if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) {
|
||||||
try neighborLists[neighbor].append(main.globalAllocator, result);
|
try neighborLists[neighbor].append(main.globalAllocator, result);
|
||||||
@ -106,7 +115,11 @@ pub const ChannelChunk = struct {
|
|||||||
defer lightQueue.deinit();
|
defer lightQueue.deinit();
|
||||||
for(lights) |pos| {
|
for(lights) |pos| {
|
||||||
const index = chunk.getIndex(pos[0], pos[1], pos[2]);
|
const index = chunk.getIndex(pos[0], pos[1], pos[2]);
|
||||||
try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)});
|
if(self.channel.isSun()) {
|
||||||
|
try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = 255});
|
||||||
|
} else {
|
||||||
|
try lightQueue.enqueue(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(checkNeighbors) {
|
if(checkNeighbors) {
|
||||||
for(0..6) |neighbor| {
|
for(0..6) |neighbor| {
|
||||||
@ -140,7 +153,9 @@ pub const ChannelChunk = struct {
|
|||||||
const index = chunk.getIndex(x, y, z);
|
const index = chunk.getIndex(x, y, z);
|
||||||
const neighborIndex = chunk.getIndex(otherX, otherY, otherZ);
|
const neighborIndex = chunk.getIndex(otherX, otherY, otherZ);
|
||||||
var value: u8 = neighborLightChunk.data[neighborIndex].load(.Unordered);
|
var value: u8 = neighborLightChunk.data[neighborIndex].load(.Unordered);
|
||||||
value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
|
if(!self.channel.isSun() or neighbor != chunk.Neighbors.dirUp or value != 255) {
|
||||||
|
value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
|
||||||
|
}
|
||||||
if(value == 0) continue;
|
if(value == 0) continue;
|
||||||
var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255);
|
var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255);
|
||||||
absorption *|= @intCast(self.ch.pos.voxelSize);
|
absorption *|= @intCast(self.ch.pos.voxelSize);
|
||||||
|
@ -1225,6 +1225,79 @@ pub const Protocols = struct {
|
|||||||
try conn.sendImportant(id, data);
|
try conn.sendImportant(id, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
pub const lightMapRequest = struct {
|
||||||
|
pub const id: u8 = 11;
|
||||||
|
fn receive(conn: *Connection, data: []const u8) !void {
|
||||||
|
var remaining = data[0..];
|
||||||
|
while(remaining.len >= 9) {
|
||||||
|
const request = main.server.terrain.SurfaceMap.MapFragmentPosition{
|
||||||
|
.wx = std.mem.readInt(i32, remaining[0..4], .big),
|
||||||
|
.wz = std.mem.readInt(i32, remaining[4..8], .big),
|
||||||
|
.voxelSize = @as(u31, 1) << @intCast(std.mem.readInt(u8, remaining[8..9], .big)),
|
||||||
|
.voxelSizeShift = @intCast(std.mem.readInt(u8, remaining[8..9], .big)),
|
||||||
|
};
|
||||||
|
if(conn.user) |user| {
|
||||||
|
try main.server.world.?.queueLightMap(request, user);
|
||||||
|
}
|
||||||
|
remaining = remaining[9..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn sendRequest(conn: *Connection, requests: []main.server.terrain.SurfaceMap.MapFragmentPosition) !void {
|
||||||
|
if(requests.len == 0) return;
|
||||||
|
const data = try main.stackAllocator.alloc(u8, 9*requests.len);
|
||||||
|
defer main.stackAllocator.free(data);
|
||||||
|
var remaining = data;
|
||||||
|
for(requests) |req| {
|
||||||
|
std.mem.writeInt(i32, remaining[0..4], req.wx, .big);
|
||||||
|
std.mem.writeInt(i32, remaining[4..8], req.wz, .big);
|
||||||
|
std.mem.writeInt(u8, remaining[8..9], req.voxelSizeShift, .big);
|
||||||
|
remaining = remaining[9..];
|
||||||
|
}
|
||||||
|
try conn.sendImportant(id, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub const lightMapTransmission = struct {
|
||||||
|
pub const id: u8 = 12;
|
||||||
|
fn receive(_: *Connection, _data: []const u8) !void {
|
||||||
|
var data = _data;
|
||||||
|
const pos = main.server.terrain.SurfaceMap.MapFragmentPosition{
|
||||||
|
.wx = std.mem.readInt(i32, data[0..4], .big),
|
||||||
|
.wz = std.mem.readInt(i32, data[4..8], .big),
|
||||||
|
.voxelSize = @as(u31, 1) << @intCast(std.mem.readInt(u8, data[8..9], .big)),
|
||||||
|
.voxelSizeShift = @intCast(std.mem.readInt(u8, data[8..9], .big)),
|
||||||
|
};
|
||||||
|
const _inflatedData = try main.stackAllocator.alloc(u8, main.server.terrain.LightMap.LightMapFragment.mapSize*main.server.terrain.LightMap.LightMapFragment.mapSize*2);
|
||||||
|
defer main.stackAllocator.free(_inflatedData);
|
||||||
|
const _inflatedLen = try utils.Compression.inflateTo(_inflatedData, data[9..]);
|
||||||
|
if(_inflatedLen != main.server.terrain.LightMap.LightMapFragment.mapSize*main.server.terrain.LightMap.LightMapFragment.mapSize*2) {
|
||||||
|
std.log.err("Transmission of light map has invalid size: {}. Input data: {any}, After inflate: {any}", .{_inflatedLen, data, _inflatedData[0.._inflatedLen]});
|
||||||
|
}
|
||||||
|
data = _inflatedData;
|
||||||
|
const map = try main.globalAllocator.create(main.server.terrain.LightMap.LightMapFragment);
|
||||||
|
map.init(pos.wx, pos.wz, pos.voxelSize);
|
||||||
|
_ = map.refCount.fetchAdd(1, .Monotonic);
|
||||||
|
for(&map.startHeight) |*val| {
|
||||||
|
val.* = std.mem.readInt(i16, data[0..2], .big);
|
||||||
|
data = data[2..];
|
||||||
|
}
|
||||||
|
try renderer.RenderStructure.updateLightMap(map);
|
||||||
|
}
|
||||||
|
pub fn sendLightMap(conn: *Connection, map: *main.server.terrain.LightMap.LightMapFragment) Allocator.Error!void {
|
||||||
|
var uncompressedData: [@sizeOf(@TypeOf(map.startHeight))]u8 = undefined; // TODO: #15280
|
||||||
|
for(&map.startHeight, 0..) |val, i| {
|
||||||
|
std.mem.writeInt(i16, uncompressedData[2*i..][0..2], val, .big);
|
||||||
|
}
|
||||||
|
const compressedData = try utils.Compression.deflate(main.stackAllocator, &uncompressedData);
|
||||||
|
defer main.stackAllocator.free(compressedData);
|
||||||
|
const data = try main.stackAllocator.alloc(u8, 9 + compressedData.len);
|
||||||
|
defer main.stackAllocator.free(data);
|
||||||
|
@memcpy(data[9..], compressedData);
|
||||||
|
std.mem.writeInt(i32, data[0..4], map.pos.wx, .big);
|
||||||
|
std.mem.writeInt(i32, data[4..8], map.pos.wz, .big);
|
||||||
|
std.mem.writeInt(u8, data[8..9], map.pos.voxelSizeShift, .big);
|
||||||
|
try conn.sendImportant(id, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
193
src/renderer.zig
193
src/renderer.zig
@ -19,6 +19,7 @@ const settings = @import("settings.zig");
|
|||||||
const utils = @import("utils.zig");
|
const utils = @import("utils.zig");
|
||||||
const vec = @import("vec.zig");
|
const vec = @import("vec.zig");
|
||||||
const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring;
|
const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring;
|
||||||
|
const LightMap = main.server.terrain.LightMap;
|
||||||
const Vec2f = vec.Vec2f;
|
const Vec2f = vec.Vec2f;
|
||||||
const Vec3i = vec.Vec3i;
|
const Vec3i = vec.Vec3i;
|
||||||
const Vec3f = vec.Vec3f;
|
const Vec3f = vec.Vec3f;
|
||||||
@ -872,9 +873,11 @@ pub const RenderStructure = struct {
|
|||||||
const storageSize = 32;
|
const storageSize = 32;
|
||||||
const storageMask = storageSize - 1;
|
const storageMask = storageSize - 1;
|
||||||
var storageLists: [settings.highestLOD + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined;
|
var storageLists: [settings.highestLOD + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined;
|
||||||
|
var mapStorageLists: [settings.highestLOD + 1]*[storageSize*storageSize]Atomic(?*LightMap.LightMapFragment) = undefined;
|
||||||
var meshList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
var meshList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
||||||
var priorityMeshUpdateList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
var priorityMeshUpdateList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
||||||
pub var updatableList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
pub var updatableList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
||||||
|
var mapUpdatableList = std.ArrayList(*LightMap.LightMapFragment).init(main.globalAllocator);
|
||||||
var clearList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
var clearList = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.globalAllocator);
|
||||||
var lastPx: i32 = 0;
|
var lastPx: i32 = 0;
|
||||||
var lastPy: i32 = 0;
|
var lastPy: i32 = 0;
|
||||||
@ -900,6 +903,10 @@ pub const RenderStructure = struct {
|
|||||||
val.rendered = false;
|
val.rendered = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(&mapStorageLists) |*mapStorageList| {
|
||||||
|
mapStorageList.* = try main.globalAllocator.create([storageSize*storageSize]Atomic(?*LightMap.LightMapFragment));
|
||||||
|
@memset(mapStorageList.*, Atomic(?*LightMap.LightMapFragment).init(null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
@ -917,10 +924,17 @@ pub const RenderStructure = struct {
|
|||||||
for(storageLists) |storageList| {
|
for(storageLists) |storageList| {
|
||||||
main.globalAllocator.destroy(storageList);
|
main.globalAllocator.destroy(storageList);
|
||||||
}
|
}
|
||||||
|
for(mapStorageLists) |mapStorageList| {
|
||||||
|
main.globalAllocator.destroy(mapStorageList);
|
||||||
|
}
|
||||||
for(updatableList.items) |mesh| {
|
for(updatableList.items) |mesh| {
|
||||||
mesh.decreaseRefCount();
|
mesh.decreaseRefCount();
|
||||||
}
|
}
|
||||||
updatableList.deinit();
|
updatableList.deinit();
|
||||||
|
for(mapUpdatableList.items) |map| {
|
||||||
|
map.decreaseRefCount();
|
||||||
|
}
|
||||||
|
mapUpdatableList.deinit();
|
||||||
for(priorityMeshUpdateList.items) |mesh| {
|
for(priorityMeshUpdateList.items) |mesh| {
|
||||||
mesh.decreaseRefCount();
|
mesh.decreaseRefCount();
|
||||||
}
|
}
|
||||||
@ -946,6 +960,26 @@ pub const RenderStructure = struct {
|
|||||||
return &storageLists[lod][@intCast(index)];
|
return &storageLists[lod][@intCast(index)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getMapPieceLocation(x: i32, z: i32, voxelSize: u31) *Atomic(?*LightMap.LightMapFragment) {
|
||||||
|
const lod = std.math.log2_int(u31, voxelSize);
|
||||||
|
var xIndex = x >> lod+LightMap.LightMapFragment.mapShift;
|
||||||
|
var zIndex = z >> lod+LightMap.LightMapFragment.mapShift;
|
||||||
|
xIndex &= storageMask;
|
||||||
|
zIndex &= storageMask;
|
||||||
|
const index = xIndex*storageSize + zIndex;
|
||||||
|
return &(&mapStorageLists)[lod][@intCast(index)];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLightMapPieceAndIncreaseRefCount(x: i32, z: i32, voxelSize: u31) ?*LightMap.LightMapFragment {
|
||||||
|
const result: *LightMap.LightMapFragment = getMapPieceLocation(x, z, voxelSize).load(.Acquire) orelse return null;
|
||||||
|
var refCount: u16 = 1;
|
||||||
|
while(result.refCount.cmpxchgWeak(refCount, refCount+1, .Monotonic, .Monotonic)) |otherVal| {
|
||||||
|
if(otherVal == 0) return null;
|
||||||
|
refCount = otherVal;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block {
|
fn getBlockFromRenderThread(x: i32, y: i32, z: i32) ?blocks.Block {
|
||||||
const node = RenderStructure.getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=1});
|
const node = RenderStructure.getNodeFromRenderThread(.{.wx = x, .wy = y, .wz = z, .voxelSize=1});
|
||||||
const mesh = node.mesh.load(.Acquire) orelse return null;
|
const mesh = node.mesh.load(.Acquire) orelse return null;
|
||||||
@ -1049,6 +1083,28 @@ pub const RenderStructure = struct {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn isMapInRenderDistance(pos: LightMap.MapFragmentPosition) bool {
|
||||||
|
const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize;
|
||||||
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize)*pos.voxelSize;
|
||||||
|
const mask: i32 = size - 1;
|
||||||
|
const invMask: i32 = ~mask;
|
||||||
|
|
||||||
|
const minX = lastPx-%maxRenderDistance & invMask;
|
||||||
|
const maxX = lastPx+%maxRenderDistance+%size & invMask;
|
||||||
|
if(pos.wx < minX) return false;
|
||||||
|
if(pos.wx >= maxX) return false;
|
||||||
|
var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx);
|
||||||
|
deltaX = @max(0, deltaX - size/2);
|
||||||
|
|
||||||
|
const maxZRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX);
|
||||||
|
if(maxZRenderDistance == 0) return false;
|
||||||
|
const minZ = lastPz-%maxZRenderDistance & invMask;
|
||||||
|
const maxZ = lastPz+%maxZRenderDistance+%size & invMask;
|
||||||
|
if(pos.wz < minZ) return false;
|
||||||
|
if(pos.wz >= maxZ) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32) !void {
|
fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32) !void {
|
||||||
for(0..storageLists.len) |_lod| {
|
for(0..storageLists.len) |_lod| {
|
||||||
const lod: u5 = @intCast(_lod);
|
const lod: u5 = @intCast(_lod);
|
||||||
@ -1121,9 +1177,67 @@ pub const RenderStructure = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(0..mapStorageLists.len) |_lod| {
|
||||||
|
const lod: u5 = @intCast(_lod);
|
||||||
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
||||||
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
||||||
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod;
|
||||||
|
const mask: i32 = size - 1;
|
||||||
|
const invMask: i32 = ~mask;
|
||||||
|
|
||||||
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize);
|
||||||
|
|
||||||
|
const minX = olderPx-%maxRenderDistanceOld & invMask;
|
||||||
|
const maxX = olderPx+%maxRenderDistanceOld+%size & invMask;
|
||||||
|
var x = minX;
|
||||||
|
while(x != maxX): (x +%= size) {
|
||||||
|
const xIndex = @divExact(x, size) & storageMask;
|
||||||
|
var deltaXNew: i64 = @abs(x +% size/2 -% lastPx);
|
||||||
|
deltaXNew = @max(0, deltaXNew - size/2);
|
||||||
|
var deltaXOld: i64 = @abs(x +% size/2 -% olderPx);
|
||||||
|
deltaXOld = @max(0, deltaXOld - size/2);
|
||||||
|
var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
||||||
|
if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2;
|
||||||
|
var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
||||||
|
if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2;
|
||||||
|
|
||||||
|
const minZOld = olderPz-%maxZRenderDistanceOld & invMask;
|
||||||
|
const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask;
|
||||||
|
const minZNew = lastPz-%maxZRenderDistanceNew & invMask;
|
||||||
|
const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask;
|
||||||
|
|
||||||
|
var zValues: [storageSize]i32 = undefined;
|
||||||
|
var zValuesLen: usize = 0;
|
||||||
|
if(minZNew -% minZOld > 0) {
|
||||||
|
var z = minZOld;
|
||||||
|
while(z != minZNew and z != maxZOld): (z +%= size) {
|
||||||
|
zValues[zValuesLen] = z;
|
||||||
|
zValuesLen += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(maxZOld -% maxZNew > 0) {
|
||||||
|
var z = minZOld +% @max(0, maxZNew -% minZOld);
|
||||||
|
while(z != maxZOld): (z +%= size) {
|
||||||
|
zValues[zValuesLen] = z;
|
||||||
|
zValuesLen += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(zValues[0..zValuesLen]) |z| {
|
||||||
|
const zIndex = @divExact(z, size) & storageMask;
|
||||||
|
const index = xIndex*storageSize + zIndex;
|
||||||
|
|
||||||
|
const mapAtomic = &mapStorageLists[_lod][@intCast(index)];
|
||||||
|
if(mapAtomic.load(.Acquire)) |map| {
|
||||||
|
mapAtomic.store(null, .Release);
|
||||||
|
map.decreaseRefCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !void {
|
fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition), mapRequests: *std.ArrayList(LightMap.MapFragmentPosition)) !void {
|
||||||
for(0..storageLists.len) |_lod| {
|
for(0..storageLists.len) |_lod| {
|
||||||
const lod: u5 = @intCast(_lod);
|
const lod: u5 = @intCast(_lod);
|
||||||
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
||||||
@ -1194,6 +1308,63 @@ pub const RenderStructure = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(0..mapStorageLists.len) |_lod| {
|
||||||
|
const lod: u5 = @intCast(_lod);
|
||||||
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
||||||
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
||||||
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod;
|
||||||
|
const mask: i32 = size - 1;
|
||||||
|
const invMask: i32 = ~mask;
|
||||||
|
|
||||||
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size-1, size) + 2 <= storageSize);
|
||||||
|
|
||||||
|
const minX = 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 maxZRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
||||||
|
if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2;
|
||||||
|
var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
||||||
|
if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2;
|
||||||
|
|
||||||
|
const minZOld = olderPz-%maxZRenderDistanceOld & invMask;
|
||||||
|
const maxZOld = olderPz+%maxZRenderDistanceOld+%size & invMask;
|
||||||
|
const minZNew = lastPz-%maxZRenderDistanceNew & invMask;
|
||||||
|
const maxZNew = lastPz+%maxZRenderDistanceNew+%size & invMask;
|
||||||
|
|
||||||
|
var zValues: [storageSize]i32 = undefined;
|
||||||
|
var zValuesLen: usize = 0;
|
||||||
|
if(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 + zIndex;
|
||||||
|
const pos = LightMap.MapFragmentPosition{.wx=x, .wz=z, .voxelSize=@as(u31, 1)<<lod, .voxelSizeShift = lod};
|
||||||
|
|
||||||
|
const node = &mapStorageLists[_lod][@intCast(index)];
|
||||||
|
std.debug.assert(node.load(.Acquire) == null);
|
||||||
|
try mapRequests.append(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32) ![]*chunk.meshing.ChunkMesh {
|
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32) ![]*chunk.meshing.ChunkMesh {
|
||||||
@ -1204,6 +1375,8 @@ pub const RenderStructure = struct {
|
|||||||
|
|
||||||
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.globalAllocator);
|
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.globalAllocator);
|
||||||
defer meshRequests.deinit();
|
defer meshRequests.deinit();
|
||||||
|
var mapRequests = std.ArrayList(LightMap.MapFragmentPosition).init(main.globalAllocator);
|
||||||
|
defer mapRequests.deinit();
|
||||||
|
|
||||||
const olderPx = lastPx;
|
const olderPx = lastPx;
|
||||||
const olderPy = lastPy;
|
const olderPy = lastPy;
|
||||||
@ -1217,9 +1390,10 @@ pub const RenderStructure = struct {
|
|||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
try freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
|
try freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
|
||||||
|
|
||||||
try createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests);
|
try createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests, &mapRequests);
|
||||||
|
|
||||||
// Make requests as soon as possible to reduce latency:
|
// Make requests as soon as possible to reduce latency:
|
||||||
|
try network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items);
|
||||||
try network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items);
|
try network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items);
|
||||||
|
|
||||||
// Does occlusion using a breadth-first search that caches an on-screen visibility rectangle.
|
// Does occlusion using a breadth-first search that caches an on-screen visibility rectangle.
|
||||||
@ -1559,6 +1733,15 @@ pub const RenderStructure = struct {
|
|||||||
try mesh.uploadData();
|
try mesh.uploadData();
|
||||||
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
|
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
|
||||||
}
|
}
|
||||||
|
while(mapUpdatableList.popOrNull()) |map| {
|
||||||
|
if(!isMapInRenderDistance(map.pos)) {
|
||||||
|
map.decreaseRefCount();
|
||||||
|
} else {
|
||||||
|
if(getMapPieceLocation(map.pos.wx, map.pos.wz, map.pos.voxelSize).swap(map, .AcqRel)) |old| {
|
||||||
|
old.decreaseRefCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
while(updatableList.items.len != 0) {
|
while(updatableList.items.len != 0) {
|
||||||
// TODO: Find a faster solution than going through the entire list every frame.
|
// TODO: Find a faster solution than going through the entire list every frame.
|
||||||
var closestPriority: f32 = -std.math.floatMax(f32);
|
var closestPriority: f32 = -std.math.floatMax(f32);
|
||||||
@ -1707,4 +1890,10 @@ pub const RenderStructure = struct {
|
|||||||
pub fn updateChunkMesh(mesh: *chunk.Chunk) !void {
|
pub fn updateChunkMesh(mesh: *chunk.Chunk) !void {
|
||||||
try MeshGenerationTask.schedule(mesh);
|
try MeshGenerationTask.schedule(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn updateLightMap(map: *LightMap.LightMapFragment) !void {
|
||||||
|
mutex.lock();
|
||||||
|
defer mutex.unlock();
|
||||||
|
try mapUpdatableList.append(map);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
82
src/server/terrain/LightMap.zig
Normal file
82
src/server/terrain/LightMap.zig
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Atomic = std.atomic.Value;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const main = @import("root");
|
||||||
|
const Chunk = main.chunk.Chunk;
|
||||||
|
const ChunkPosition = main.chunk.ChunkPosition;
|
||||||
|
const Cache = main.utils.Cache;
|
||||||
|
const JsonElement = main.JsonElement;
|
||||||
|
|
||||||
|
const terrain = @import("terrain.zig");
|
||||||
|
const TerrainGenerationProfile = terrain.TerrainGenerationProfile;
|
||||||
|
pub const MapFragmentPosition = terrain.SurfaceMap.MapFragmentPosition;
|
||||||
|
const Biome = terrain.biomes.Biome;
|
||||||
|
|
||||||
|
/// Generates and stores the light start position for each block column.
|
||||||
|
pub const LightMapFragment = struct {
|
||||||
|
pub const mapShift = 8;
|
||||||
|
pub const mapSize = 1 << mapShift;
|
||||||
|
pub const mapMask = mapSize - 1;
|
||||||
|
|
||||||
|
startHeight: [mapSize*mapSize]i16 = undefined,
|
||||||
|
pos: MapFragmentPosition,
|
||||||
|
|
||||||
|
refCount: Atomic(u16) = Atomic(u16).init(0),
|
||||||
|
|
||||||
|
pub fn init(self: *LightMapFragment, wx: i32, wz: i32, voxelSize: u31) void {
|
||||||
|
self.* = .{
|
||||||
|
.pos = MapFragmentPosition.init(wx, wz, voxelSize),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decreaseRefCount(self: *LightMapFragment) void {
|
||||||
|
if(@atomicRmw(u16, &self.refCount.raw, .Sub, 1, .Monotonic) == 1) {
|
||||||
|
main.globalAllocator.destroy(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeight(self: *LightMapFragment, wx: i32, wz: i32) i32 {
|
||||||
|
const xIndex = wx>>self.pos.voxelSizeShift & mapMask;
|
||||||
|
const zIndex = wz>>self.pos.voxelSizeShift & mapMask;
|
||||||
|
return self.startHeight[@as(usize, @intCast(xIndex)) << mapShift | @as(usize, @intCast(zIndex))];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const cacheSize = 1 << 6; // Must be a power of 2!
|
||||||
|
const cacheMask = cacheSize - 1;
|
||||||
|
const associativity = 8; // ~100MiB MiB Cache size
|
||||||
|
var cache: Cache(LightMapFragment, cacheSize, associativity, LightMapFragment.decreaseRefCount) = .{};
|
||||||
|
|
||||||
|
fn cacheInit(pos: MapFragmentPosition) !*LightMapFragment {
|
||||||
|
const mapFragment = try main.globalAllocator.create(LightMapFragment);
|
||||||
|
mapFragment.init(pos.wx, pos.wz, pos.voxelSize);
|
||||||
|
const surfaceMap = try terrain.SurfaceMap.getOrGenerateFragment(pos.wx, pos.wz, pos.voxelSize);
|
||||||
|
defer surfaceMap.deinit();
|
||||||
|
comptime std.debug.assert(LightMapFragment.mapSize == terrain.SurfaceMap.MapFragment.mapSize);
|
||||||
|
for(0..LightMapFragment.mapSize) |x| {
|
||||||
|
for(0..LightMapFragment.mapSize) |z| {
|
||||||
|
const baseHeight: i16 = std.math.lossyCast(i16, surfaceMap.heightMap[x][z]);
|
||||||
|
mapFragment.startHeight[x << LightMapFragment.mapShift | z] = @max(0, baseHeight +| 16); // Simple heuristic. TODO: Update this value once chunks get generated in the region.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = @atomicRmw(u16, &mapFragment.refCount.raw, .Add, 1, .Monotonic);
|
||||||
|
return mapFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit() void {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call decreaseRefCount on the result.
|
||||||
|
pub fn getOrGenerateFragment(wx: i32, wz: i32, voxelSize: u31) !*LightMapFragment {
|
||||||
|
const compare = MapFragmentPosition.init(
|
||||||
|
wx & ~@as(i32, LightMapFragment.mapMask*voxelSize | voxelSize-1),
|
||||||
|
wz & ~@as(i32, LightMapFragment.mapMask*voxelSize | voxelSize-1),
|
||||||
|
voxelSize
|
||||||
|
);
|
||||||
|
const result = try cache.findOrCreate(compare, cacheInit);
|
||||||
|
std.debug.assert(@atomicRmw(u16, &result.refCount.raw, .Add, 1, .Monotonic) != 0);
|
||||||
|
return result;
|
||||||
|
}
|
@ -12,7 +12,7 @@ const terrain = @import("terrain.zig");
|
|||||||
const TerrainGenerationProfile = terrain.TerrainGenerationProfile;
|
const TerrainGenerationProfile = terrain.TerrainGenerationProfile;
|
||||||
const Biome = terrain.biomes.Biome;
|
const Biome = terrain.biomes.Biome;
|
||||||
|
|
||||||
const MapFragmentPosition = struct {
|
pub const MapFragmentPosition = struct {
|
||||||
wx: i32,
|
wx: i32,
|
||||||
wz: i32,
|
wz: i32,
|
||||||
voxelSize: u31,
|
voxelSize: u31,
|
||||||
@ -30,12 +30,10 @@ const MapFragmentPosition = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn equals(self: MapFragmentPosition, other: anytype) bool {
|
pub fn equals(self: MapFragmentPosition, other: anytype) bool {
|
||||||
if(@TypeOf(other) == ?*MapFragment) {
|
if(other) |ch| {
|
||||||
if(other) |ch| {
|
return self.wx == ch.pos.wx and self.wz == ch.pos.wz and self.voxelSize == ch.pos.voxelSize;
|
||||||
return self.wx == ch.pos.wx and self.wz == ch.pos.wz and self.voxelSize == ch.pos.voxelSize;
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
|
||||||
} else @compileError("Unsupported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hashCode(self: MapFragmentPosition) u32 {
|
pub fn hashCode(self: MapFragmentPosition) u32 {
|
||||||
|
@ -12,6 +12,8 @@ pub const ClimateMap = @import("ClimateMap.zig");
|
|||||||
|
|
||||||
pub const SurfaceMap = @import("SurfaceMap.zig");
|
pub const SurfaceMap = @import("SurfaceMap.zig");
|
||||||
|
|
||||||
|
pub const LightMap = @import("LightMap.zig");
|
||||||
|
|
||||||
pub const CaveBiomeMap = @import("CaveBiomeMap.zig");
|
pub const CaveBiomeMap = @import("CaveBiomeMap.zig");
|
||||||
|
|
||||||
pub const CaveMap = @import("CaveMap.zig");
|
pub const CaveMap = @import("CaveMap.zig");
|
||||||
@ -146,4 +148,5 @@ pub fn deinit() void {
|
|||||||
CaveMap.deinit();
|
CaveMap.deinit();
|
||||||
ClimateMap.deinit();
|
ClimateMap.deinit();
|
||||||
SurfaceMap.deinit();
|
SurfaceMap.deinit();
|
||||||
|
LightMap.deinit();
|
||||||
}
|
}
|
@ -103,6 +103,62 @@ const ChunkManager = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LightMapLoadTask = struct {
|
||||||
|
pos: terrain.SurfaceMap.MapFragmentPosition,
|
||||||
|
creationTime: i64,
|
||||||
|
source: ?*User,
|
||||||
|
|
||||||
|
const vtable = utils.ThreadPool.VTable{
|
||||||
|
.getPriority = @ptrCast(&getPriority),
|
||||||
|
.isStillNeeded = @ptrCast(&isStillNeeded),
|
||||||
|
.run = @ptrCast(&run),
|
||||||
|
.clean = @ptrCast(&clean),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn schedule(pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void {
|
||||||
|
const task = try main.globalAllocator.create(LightMapLoadTask);
|
||||||
|
task.* = LightMapLoadTask {
|
||||||
|
.pos = pos,
|
||||||
|
.creationTime = std.time.milliTimestamp(),
|
||||||
|
.source = source,
|
||||||
|
};
|
||||||
|
try main.threadPool.addTask(task, &vtable);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPriority(self: *LightMapLoadTask) f32 {
|
||||||
|
if(self.source) |user| {
|
||||||
|
const pos = ChunkPosition{.wx = self.pos.wx, .wy = @intFromFloat(user.player.pos[1]), .wz = self.pos.wz, .voxelSize = self.pos.voxelSize};
|
||||||
|
return pos.getPriority(user.player.pos) + 100000;
|
||||||
|
} else {
|
||||||
|
return std.math.floatMax(f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isStillNeeded(self: *LightMapLoadTask) bool {
|
||||||
|
_ = self; // TODO: Do these tasks need to be culled?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: *LightMapLoadTask) Allocator.Error!void {
|
||||||
|
defer self.clean();
|
||||||
|
const map = try terrain.LightMap.getOrGenerateFragment(self.pos.wx, self.pos.wz, self.pos.voxelSize);
|
||||||
|
defer map.decreaseRefCount();
|
||||||
|
if(self.source) |source| {
|
||||||
|
try main.network.Protocols.lightMapTransmission.sendLightMap(source.conn, map);
|
||||||
|
} else {
|
||||||
|
server.mutex.lock();
|
||||||
|
defer server.mutex.unlock();
|
||||||
|
for(server.users.items) |user| {
|
||||||
|
try main.network.Protocols.lightMapTransmission.sendLightMap(user.conn, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clean(self: *LightMapLoadTask) void {
|
||||||
|
main.globalAllocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(world: *ServerWorld, settings: JsonElement) !ChunkManager {
|
pub fn init(world: *ServerWorld, settings: JsonElement) !ChunkManager {
|
||||||
const self = ChunkManager {
|
const self = ChunkManager {
|
||||||
.world = world,
|
.world = world,
|
||||||
@ -135,6 +191,11 @@ const ChunkManager = struct {
|
|||||||
self.terrainGenerationProfile.deinit();
|
self.terrainGenerationProfile.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queueLightMap(self: ChunkManager, pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void {
|
||||||
|
_ = self;
|
||||||
|
try LightMapLoadTask.schedule(pos, source);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn queueChunk(self: ChunkManager, pos: ChunkPosition, source: ?*User) !void {
|
pub fn queueChunk(self: ChunkManager, pos: ChunkPosition, source: ?*User) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
try ChunkLoadTask.schedule(pos, source);
|
try ChunkLoadTask.schedule(pos, source);
|
||||||
@ -531,6 +592,10 @@ pub const ServerWorld = struct {
|
|||||||
try self.chunkManager.queueChunk(pos, source);
|
try self.chunkManager.queueChunk(pos, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queueLightMap(self: *ServerWorld, pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) !void {
|
||||||
|
try self.chunkManager.queueLightMap(pos, source);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seek() !void {
|
pub fn seek() !void {
|
||||||
// TODO: Remove this MetaChunk stuff. It wasn't really useful and made everything needlessly complicated.
|
// TODO: Remove this MetaChunk stuff. It wasn't really useful and made everything needlessly complicated.
|
||||||
// // Care about the metaChunks:
|
// // Care about the metaChunks:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user