Cubyz/src/renderer/chunk_meshing.zig
IntegratedQuantum c2ef9aba62 Remove the root . . . . . . . . . . import, replacing it with @import("main")
To fully solve the ZLS issues I had to also add the import to the other executables.
2025-03-27 21:16:35 +01:00

1650 lines
64 KiB
Zig

const std = @import("std");
const Atomic = std.atomic.Value;
const main = @import("main");
const blocks = main.blocks;
const Block = blocks.Block;
const chunk = main.chunk;
const game = main.game;
const models = main.models;
const QuadIndex = models.QuadIndex;
const renderer = main.renderer;
const graphics = main.graphics;
const c = graphics.c;
const Shader = graphics.Shader;
const SSBO = graphics.SSBO;
const lighting = @import("lighting.zig");
const settings = main.settings;
const vec = main.vec;
const Vec2f = vec.Vec2f;
const Vec3i = vec.Vec3i;
const Vec3f = vec.Vec3f;
const Vec3d = vec.Vec3d;
const Mat4f = vec.Mat4f;
const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring;
const mesh_storage = @import("mesh_storage.zig");
var shader: Shader = undefined;
var transparentShader: Shader = undefined;
const UniformStruct = struct {
projectionMatrix: c_int,
viewMatrix: c_int,
playerPositionInteger: c_int,
playerPositionFraction: c_int,
screenSize: c_int,
ambientLight: c_int,
contrast: c_int,
@"fog.color": c_int,
@"fog.density": c_int,
@"fog.fogLower": c_int,
@"fog.fogHigher": c_int,
texture_sampler: c_int,
emissionSampler: c_int,
reflectivityAndAbsorptionSampler: c_int,
reflectionMap: c_int,
reflectionMapSize: c_int,
lodDistance: c_int,
zNear: c_int,
zFar: c_int,
};
pub var uniforms: UniformStruct = undefined;
pub var transparentUniforms: UniformStruct = undefined;
pub var commandShader: Shader = undefined;
pub var commandUniforms: struct {
chunkIDIndex: c_int,
commandIndexStart: c_int,
size: c_int,
isTransparent: c_int,
playerPositionInteger: c_int,
onlyDrawPreviouslyInvisible: c_int,
lodDistance: c_int,
} = undefined;
pub var occlusionTestShader: Shader = undefined;
pub var occlusionTestUniforms: struct {
projectionMatrix: c_int,
viewMatrix: c_int,
playerPositionInteger: c_int,
playerPositionFraction: c_int,
} = undefined;
pub var vao: c_uint = undefined;
var vbo: c_uint = undefined;
pub var faceBuffer: graphics.LargeBuffer(FaceData) = undefined;
pub var lightBuffer: graphics.LargeBuffer(u32) = undefined;
pub var chunkBuffer: graphics.LargeBuffer(ChunkData) = undefined;
pub var commandBuffer: graphics.LargeBuffer(IndirectData) = undefined;
pub var chunkIDBuffer: graphics.LargeBuffer(u32) = undefined;
pub var quadsDrawn: usize = 0;
pub var transparentQuadsDrawn: usize = 0;
pub fn init() void {
lighting.init();
shader = Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs", "", &uniforms);
transparentShader = Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/transparent_fragment.fs", "#define transparent\n", &transparentUniforms);
commandShader = Shader.initComputeAndGetUniforms("assets/cubyz/shaders/chunks/fillIndirectBuffer.glsl", "", &commandUniforms);
occlusionTestShader = Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/occlusionTestVertex.vs", "assets/cubyz/shaders/chunks/occlusionTestFragment.fs", "", &occlusionTestUniforms);
var rawData: [6*3 << (3*chunk.chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block
const lut = [_]u32{0, 2, 1, 1, 2, 3};
for(0..rawData.len) |i| {
rawData[i] = @as(u32, @intCast(i))/6*4 + lut[i%6];
}
c.glGenVertexArrays(1, &vao);
c.glBindVertexArray(vao);
c.glGenBuffers(1, &vbo);
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbo);
c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, rawData.len*@sizeOf(u32), &rawData, c.GL_STATIC_DRAW);
c.glBindVertexArray(0);
faceBuffer.init(main.globalAllocator, 1 << 20, 3);
lightBuffer.init(main.globalAllocator, 1 << 20, 10);
chunkBuffer.init(main.globalAllocator, 1 << 20, 6);
commandBuffer.init(main.globalAllocator, 1 << 20, 8);
chunkIDBuffer.init(main.globalAllocator, 1 << 20, 9);
}
pub fn deinit() void {
lighting.deinit();
shader.deinit();
transparentShader.deinit();
commandShader.deinit();
c.glDeleteVertexArrays(1, &vao);
c.glDeleteBuffers(1, &vbo);
faceBuffer.deinit();
lightBuffer.deinit();
chunkBuffer.deinit();
commandBuffer.deinit();
chunkIDBuffer.deinit();
}
pub fn beginRender() void {
faceBuffer.beginRender();
lightBuffer.beginRender();
chunkBuffer.beginRender();
commandBuffer.beginRender();
chunkIDBuffer.beginRender();
}
pub fn endRender() void {
faceBuffer.endRender();
lightBuffer.endRender();
chunkBuffer.endRender();
commandBuffer.endRender();
chunkIDBuffer.endRender();
}
fn bindCommonUniforms(locations: *UniformStruct, projMatrix: Mat4f, ambient: Vec3f, playerPos: Vec3d) void {
c.glUniformMatrix4fv(locations.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix));
c.glUniform1i(locations.texture_sampler, 0);
c.glUniform1i(locations.emissionSampler, 1);
c.glUniform1i(locations.reflectivityAndAbsorptionSampler, 2);
c.glUniform1i(locations.reflectionMap, 4);
c.glUniform1f(locations.reflectionMapSize, renderer.reflectionCubeMapSize);
c.glUniform1f(locations.contrast, 0);
c.glUniform1f(locations.lodDistance, main.settings.@"lod0.5Distance");
c.glUniformMatrix4fv(locations.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix));
c.glUniform3f(locations.ambientLight, ambient[0], ambient[1], ambient[2]);
c.glUniform1f(locations.zNear, renderer.zNear);
c.glUniform1f(locations.zFar, renderer.zFar);
c.glUniform3i(locations.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
c.glUniform3f(locations.playerPositionFraction, @floatCast(@mod(playerPos[0], 1)), @floatCast(@mod(playerPos[1], 1)), @floatCast(@mod(playerPos[2], 1)));
}
pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, playerPos: Vec3d) void {
shader.bind();
bindCommonUniforms(&uniforms, projMatrix, ambient, playerPos);
c.glBindVertexArray(vao);
}
pub fn bindTransparentShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, playerPos: Vec3d) void {
transparentShader.bind();
c.glUniform3fv(transparentUniforms.@"fog.color", 1, @ptrCast(&game.fog.skyColor));
c.glUniform1f(transparentUniforms.@"fog.density", game.fog.density);
c.glUniform1f(transparentUniforms.@"fog.fogLower", game.fog.fogLower);
c.glUniform1f(transparentUniforms.@"fog.fogHigher", game.fog.fogHigher);
bindCommonUniforms(&transparentUniforms, projMatrix, ambient, playerPos);
c.glBindVertexArray(vao);
}
pub fn drawChunksIndirect(chunkIDs: []const u32, projMatrix: Mat4f, ambient: Vec3f, playerPos: Vec3d, transparent: bool) void {
const drawCallsEstimate: u31 = @intCast(if(transparent) chunkIDs.len else chunkIDs.len*8);
var chunkIDAllocation: main.graphics.SubAllocation = .{.start = 0, .len = 0};
chunkIDBuffer.uploadData(chunkIDs, &chunkIDAllocation);
defer chunkIDBuffer.free(chunkIDAllocation);
const allocation = commandBuffer.rawAlloc(drawCallsEstimate);
defer commandBuffer.free(allocation);
commandShader.bind();
c.glUniform1f(commandUniforms.lodDistance, main.settings.@"lod0.5Distance");
c.glUniform1ui(commandUniforms.chunkIDIndex, chunkIDAllocation.start);
c.glUniform1ui(commandUniforms.commandIndexStart, allocation.start);
c.glUniform1ui(commandUniforms.size, @intCast(chunkIDs.len));
c.glUniform1i(commandUniforms.isTransparent, @intFromBool(transparent));
c.glUniform3i(commandUniforms.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
if(!transparent) {
gpu_performance_measuring.startQuery(.chunk_rendering_previous_visible);
c.glUniform1i(commandUniforms.onlyDrawPreviouslyInvisible, 0);
c.glDispatchCompute(@intCast(@divFloor(chunkIDs.len + 63, 64)), 1, 1); // TODO: Replace with @divCeil once available
c.glMemoryBarrier(c.GL_SHADER_STORAGE_BARRIER_BIT | c.GL_COMMAND_BARRIER_BIT);
if(transparent) {
bindTransparentShaderAndUniforms(projMatrix, ambient, playerPos);
} else {
bindShaderAndUniforms(projMatrix, ambient, playerPos);
}
c.glBindBuffer(c.GL_DRAW_INDIRECT_BUFFER, commandBuffer.ssbo.bufferID);
c.glMultiDrawElementsIndirect(c.GL_TRIANGLES, c.GL_UNSIGNED_INT, @ptrFromInt(allocation.start*@sizeOf(IndirectData)), drawCallsEstimate, 0);
gpu_performance_measuring.stopQuery();
}
// Occlusion tests:
gpu_performance_measuring.startQuery(if(transparent) .transparent_rendering_occlusion_test else .chunk_rendering_occlusion_test);
occlusionTestShader.bind();
c.glUniform3i(occlusionTestUniforms.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
c.glUniform3f(occlusionTestUniforms.playerPositionFraction, @floatCast(@mod(playerPos[0], 1)), @floatCast(@mod(playerPos[1], 1)), @floatCast(@mod(playerPos[2], 1)));
c.glUniformMatrix4fv(occlusionTestUniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix));
c.glUniformMatrix4fv(occlusionTestUniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix));
c.glDepthMask(c.GL_FALSE);
c.glColorMask(c.GL_FALSE, c.GL_FALSE, c.GL_FALSE, c.GL_FALSE);
c.glBindVertexArray(vao);
c.glDrawElementsBaseVertex(c.GL_TRIANGLES, @intCast(6*6*chunkIDs.len), c.GL_UNSIGNED_INT, null, chunkIDAllocation.start*24);
c.glDepthMask(c.GL_TRUE);
c.glColorMask(c.GL_TRUE, c.GL_TRUE, c.GL_TRUE, c.GL_TRUE);
c.glMemoryBarrier(c.GL_SHADER_STORAGE_BARRIER_BIT);
gpu_performance_measuring.stopQuery();
// Draw again:
gpu_performance_measuring.startQuery(if(transparent) .transparent_rendering else .chunk_rendering_new_visible);
commandShader.bind();
c.glUniform1i(commandUniforms.onlyDrawPreviouslyInvisible, 1);
c.glDispatchCompute(@intCast(@divFloor(chunkIDs.len + 63, 64)), 1, 1); // TODO: Replace with @divCeil once available
c.glMemoryBarrier(c.GL_SHADER_STORAGE_BARRIER_BIT | c.GL_COMMAND_BARRIER_BIT);
if(transparent) {
bindTransparentShaderAndUniforms(projMatrix, ambient, playerPos);
c.glDepthMask(c.GL_FALSE);
} else {
bindShaderAndUniforms(projMatrix, ambient, playerPos);
}
c.glBindBuffer(c.GL_DRAW_INDIRECT_BUFFER, commandBuffer.ssbo.bufferID);
c.glMultiDrawElementsIndirect(c.GL_TRIANGLES, c.GL_UNSIGNED_INT, @ptrFromInt(allocation.start*@sizeOf(IndirectData)), drawCallsEstimate, 0);
gpu_performance_measuring.stopQuery();
}
pub const FaceData = extern struct {
position: packed struct(u32) {
x: u5,
y: u5,
z: u5,
isBackFace: bool,
lightIndex: u16 = 0,
},
blockAndQuad: packed struct(u32) {
texture: u16,
quadIndex: QuadIndex,
},
pub inline fn init(texture: u16, quadIndex: QuadIndex, x: i32, y: i32, z: i32, comptime backFace: bool) FaceData {
return FaceData{
.position = .{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .isBackFace = backFace},
.blockAndQuad = .{.texture = texture, .quadIndex = quadIndex},
};
}
};
pub const ChunkData = extern struct {
position: Vec3i align(16),
min: Vec3f align(16),
max: Vec3f align(16),
voxelSize: i32,
lightStart: u32,
vertexStartOpaque: u32,
faceCountsByNormalOpaque: [14]u32,
vertexStartTransparent: u32,
vertexCountTransparent: u32,
visibilityState: u32,
oldVisibilityState: u32,
};
pub const IndirectData = extern struct {
count: u32,
instanceCount: u32,
firstIndex: u32,
baseVertex: i32,
baseInstance: u32,
};
const PrimitiveMesh = struct { // MARK: PrimitiveMesh
const FaceGroups = enum(u32) {
core,
neighbor0,
neighbor1,
neighbor2,
neighbor3,
neighbor4,
neighbor5,
neighborLod0,
neighborLod1,
neighborLod2,
neighborLod3,
neighborLod4,
neighborLod5,
optional,
pub fn neighbor(n: main.chunk.Neighbor) FaceGroups {
return @enumFromInt(@intFromEnum(FaceGroups.neighbor0) + @intFromEnum(n));
}
pub fn neighborLod(n: main.chunk.Neighbor) FaceGroups {
return @enumFromInt(@intFromEnum(FaceGroups.neighborLod0) + @intFromEnum(n));
}
};
completeList: main.MultiArray(FaceData, FaceGroups) = .{},
lock: main.utils.ReadWriteLock = .{},
bufferAllocation: graphics.SubAllocation = .{.start = 0, .len = 0},
vertexCount: u31 = 0,
byNormalCount: [14]u32 = @splat(0),
wasChanged: bool = false,
min: Vec3f = undefined,
max: Vec3f = undefined,
fn deinit(self: *PrimitiveMesh) void {
faceBuffer.free(self.bufferAllocation);
self.completeList.deinit(main.globalAllocator);
}
fn replaceRange(self: *PrimitiveMesh, group: FaceGroups, items: []const FaceData) void {
self.lock.lockWrite();
self.completeList.replaceRange(main.globalAllocator, group, items);
self.lock.unlockWrite();
}
fn finish(self: *PrimitiveMesh, parent: *ChunkMesh, lightList: *main.List(u32), lightMap: *std.AutoHashMap([4]u32, u16)) void {
self.min = @splat(std.math.floatMax(f32));
self.max = @splat(-std.math.floatMax(f32));
self.lock.lockRead();
parent.lightingData[0].lock.lockRead();
parent.lightingData[1].lock.lockRead();
for(self.completeList.getEverything()) |*face| {
const light = getLight(parent, .{face.position.x, face.position.y, face.position.z}, face.blockAndQuad.texture, face.blockAndQuad.quadIndex);
const result = lightMap.getOrPut(light) catch unreachable;
if(!result.found_existing) {
result.value_ptr.* = @intCast(lightList.items.len/4);
lightList.appendSlice(&light);
}
face.position.lightIndex = result.value_ptr.*;
const basePos: Vec3f = .{
@floatFromInt(face.position.x),
@floatFromInt(face.position.y),
@floatFromInt(face.position.z),
};
for(face.blockAndQuad.quadIndex.quadInfo().corners) |cornerPos| {
self.min = @min(self.min, basePos + cornerPos);
self.max = @max(self.max, basePos + cornerPos);
}
}
parent.lightingData[0].lock.unlockRead();
parent.lightingData[1].lock.unlockRead();
self.lock.unlockRead();
}
fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 {
const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
const z = (wz >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
return mesh.lightingData[1].getValue(x, y, z) ++ mesh.lightingData[0].getValue(x, y, z);
}
fn getLightAt(parent: *ChunkMesh, x: i32, y: i32, z: i32) [6]u8 {
const wx = parent.pos.wx +% x*parent.pos.voxelSize;
const wy = parent.pos.wy +% y*parent.pos.voxelSize;
const wz = parent.pos.wz +% z*parent.pos.voxelSize;
if(x == x & chunk.chunkMask and y == y & chunk.chunkMask and z == z & chunk.chunkMask) {
return getValues(parent, wx, wy, wz);
}
const neighborMesh = mesh_storage.getMeshAndIncreaseRefCount(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = parent.pos.voxelSize}) orelse return .{0, 0, 0, 0, 0, 0};
defer neighborMesh.decreaseRefCount();
neighborMesh.lightingData[0].lock.lockRead();
neighborMesh.lightingData[1].lock.lockRead();
defer neighborMesh.lightingData[0].lock.unlockRead();
defer neighborMesh.lightingData[1].lock.unlockRead();
return getValues(neighborMesh, wx, wy, wz);
}
fn getCornerLight(parent: *ChunkMesh, pos: Vec3i, normal: Vec3f) [6]u8 {
const lightPos = @as(Vec3f, @floatFromInt(pos)) + normal*@as(Vec3f, @splat(0.5)) - @as(Vec3f, @splat(0.5));
const startPos: Vec3i = @intFromFloat(@floor(lightPos));
const interp = lightPos - @floor(lightPos);
var val: [6]f32 = .{0, 0, 0, 0, 0, 0};
var dx: i32 = 0;
while(dx <= 1) : (dx += 1) {
var dy: i32 = 0;
while(dy <= 1) : (dy += 1) {
var dz: i32 = 0;
while(dz <= 1) : (dz += 1) {
var weight: f32 = 0;
if(dx == 0) weight = 1 - interp[0] else weight = interp[0];
if(dy == 0) weight *= 1 - interp[1] else weight *= interp[1];
if(dz == 0) weight *= 1 - interp[2] else weight *= interp[2];
const lightVal: [6]u8 = getLightAt(parent, startPos[0] +% dx, startPos[1] +% dy, startPos[2] +% dz);
for(0..6) |i| {
val[i] += @as(f32, @floatFromInt(lightVal[i]))*weight;
}
}
}
}
var result: [6]u8 = undefined;
for(0..6) |i| {
result[i] = std.math.lossyCast(u8, val[i]);
}
return result;
}
fn getCornerLightAligned(parent: *ChunkMesh, pos: Vec3i, direction: chunk.Neighbor) [6]u8 { // Fast path for algined normals, leading to 4 instead of 8 light samples.
const normal: Vec3f = @floatFromInt(Vec3i{direction.relX(), direction.relY(), direction.relZ()});
const lightPos = @as(Vec3f, @floatFromInt(pos)) + normal*@as(Vec3f, @splat(0.5)) - @as(Vec3f, @splat(0.5));
const startPos: Vec3i = @intFromFloat(@floor(lightPos));
var val: [6]f32 = .{0, 0, 0, 0, 0, 0};
var dx: i32 = 0;
while(dx <= 1) : (dx += 1) {
var dy: i32 = 0;
while(dy <= 1) : (dy += 1) {
const weight: f32 = 1.0/4.0;
const finalPos = startPos +% @as(Vec3i, @intCast(@abs(direction.textureX())))*@as(Vec3i, @splat(dx)) +% @as(Vec3i, @intCast(@abs(direction.textureY()*@as(Vec3i, @splat(dy)))));
var lightVal: [6]u8 = getLightAt(parent, finalPos[0], finalPos[1], finalPos[2]);
if(parent.pos.voxelSize == 1) {
const nextVal = getLightAt(parent, finalPos[0] +% direction.relX(), finalPos[1] +% direction.relY(), finalPos[2] +% direction.relZ());
for(0..6) |i| {
const diff: u8 = @min(8, lightVal[i] -| nextVal[i]);
lightVal[i] = lightVal[i] -| diff*5/2;
}
}
for(0..6) |i| {
val[i] += @as(f32, @floatFromInt(lightVal[i]))*weight;
}
}
}
var result: [6]u8 = undefined;
for(0..6) |i| {
result[i] = std.math.lossyCast(u8, val[i]);
}
return result;
}
fn packLightValues(rawVals: [4][6]u5) [4]u32 {
var result: [4]u32 = undefined;
for(0..4) |i| {
result[i] = (@as(u32, rawVals[i][0]) << 25 |
@as(u32, rawVals[i][1]) << 20 |
@as(u32, rawVals[i][2]) << 15 |
@as(u32, rawVals[i][3]) << 10 |
@as(u32, rawVals[i][4]) << 5 |
@as(u32, rawVals[i][5]) << 0);
}
return result;
}
fn getLight(parent: *ChunkMesh, blockPos: Vec3i, textureIndex: u16, quadIndex: QuadIndex) [4]u32 {
const quadInfo = quadIndex.quadInfo();
const extraQuadInfo = quadIndex.extraQuadInfo();
const normal = quadInfo.normal;
if(!blocks.meshes.textureOcclusionData.items[textureIndex]) { // No ambient occlusion (→ no smooth lighting)
const fullValues = getLightAt(parent, blockPos[0], blockPos[1], blockPos[2]);
var rawVals: [6]u5 = undefined;
for(0..6) |i| {
rawVals[i] = std.math.lossyCast(u5, fullValues[i]/8);
}
return packLightValues(@splat(rawVals));
}
if(extraQuadInfo.hasOnlyCornerVertices) { // Fast path for simple quads.
var rawVals: [4][6]u5 = undefined;
for(0..4) |i| {
const vertexPos = quadInfo.corners[i];
const fullPos = blockPos +% @as(Vec3i, @intFromFloat(vertexPos));
const fullValues = if(extraQuadInfo.alignedNormalDirection) |dir|
getCornerLightAligned(parent, fullPos, dir)
else
getCornerLight(parent, fullPos, normal);
for(0..6) |j| {
rawVals[i][j] = std.math.lossyCast(u5, fullValues[j]/8);
}
}
return packLightValues(rawVals);
}
var cornerVals: [2][2][2][6]u8 = undefined;
{
var dx: u31 = 0;
while(dx <= 1) : (dx += 1) {
var dy: u31 = 0;
while(dy <= 1) : (dy += 1) {
var dz: u31 = 0;
while(dz <= 1) : (dz += 1) {
cornerVals[dx][dy][dz] = if(extraQuadInfo.alignedNormalDirection) |dir|
getCornerLightAligned(parent, blockPos +% Vec3i{dx, dy, dz}, dir)
else
getCornerLight(parent, blockPos +% Vec3i{dx, dy, dz}, normal);
}
}
}
}
var rawVals: [4][6]u5 = undefined;
for(0..4) |i| {
const vertexPos = quadInfo.corners[i];
const lightPos = vertexPos + @as(Vec3f, @floatFromInt(blockPos));
const interp = lightPos - @as(Vec3f, @floatFromInt(blockPos));
var val: [6]f32 = .{0, 0, 0, 0, 0, 0};
for(0..2) |dx| {
for(0..2) |dy| {
for(0..2) |dz| {
var weight: f32 = 0;
if(dx == 0) weight = 1 - interp[0] else weight = interp[0];
if(dy == 0) weight *= 1 - interp[1] else weight *= interp[1];
if(dz == 0) weight *= 1 - interp[2] else weight *= interp[2];
const lightVal: [6]u8 = cornerVals[dx][dy][dz];
for(0..6) |j| {
val[j] += @as(f32, @floatFromInt(lightVal[j]))*weight;
}
}
}
}
for(0..6) |j| {
rawVals[i][j] = std.math.lossyCast(u5, val[j]/8);
}
}
return packLightValues(rawVals);
}
fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) void {
self.lock.lockRead();
defer self.lock.unlockRead();
var len: usize = 0;
const coreList = self.completeList.getRange(.core);
len += coreList.len;
const optionalList = self.completeList.getRange(.optional);
len += optionalList.len;
var list: [6][]FaceData = undefined;
for(0..6) |i| {
if(!isNeighborLod[i]) {
list[i] = self.completeList.getRange(.neighbor(@enumFromInt(i)));
} else {
list[i] = self.completeList.getRange(.neighborLod(@enumFromInt(i)));
}
len += list[i].len;
}
const fullBuffer = faceBuffer.allocateAndMapRange(len, &self.bufferAllocation);
defer faceBuffer.unmapRange(fullBuffer);
// Sort the faces by normal to allow for backface culling on the GPU:
var i: u32 = 0;
var iStart = i;
for(0..7) |normal| {
for(coreList) |face| {
if(face.blockAndQuad.quadIndex.extraQuadInfo().alignedNormalDirection) |normalDir| {
if(normalDir.toInt() == normal) {
fullBuffer[i] = face;
i += 1;
}
} else if(normal == 6) {
fullBuffer[i] = face;
i += 1;
}
}
if(normal < 6) {
const normalDir: chunk.Neighbor = @enumFromInt(normal);
@memcpy(fullBuffer[i..][0..list[normalDir.reverse().toInt()].len], list[normalDir.reverse().toInt()]);
i += @intCast(list[normalDir.reverse().toInt()].len);
}
self.byNormalCount[normal] = i - iStart;
iStart = i;
}
for(0..7) |normal| {
for(optionalList) |face| {
if(face.blockAndQuad.quadIndex.extraQuadInfo().alignedNormalDirection) |normalDir| {
if(normalDir.toInt() == normal) {
fullBuffer[i] = face;
i += 1;
}
} else if(normal == 6) {
fullBuffer[i] = face;
i += 1;
}
}
self.byNormalCount[normal + 7] = i - iStart;
iStart = i;
}
std.debug.assert(i == fullBuffer.len);
self.vertexCount = @intCast(6*fullBuffer.len);
self.wasChanged = true;
}
};
pub const ChunkMesh = struct { // MARK: ChunkMesh
const SortingData = struct {
face: FaceData,
distance: u32,
isBackFace: bool,
shouldBeCulled: bool,
pub fn update(self: *SortingData, chunkDx: i32, chunkDy: i32, chunkDz: i32) void {
const x: i32 = self.face.position.x;
const y: i32 = self.face.position.y;
const z: i32 = self.face.position.z;
const dx = x + chunkDx;
const dy = y + chunkDy;
const dz = z + chunkDz;
self.isBackFace = self.face.position.isBackFace;
const quadIndex = self.face.blockAndQuad.quadIndex;
const normalVector = quadIndex.quadInfo().normal;
self.shouldBeCulled = vec.dot(normalVector, @floatFromInt(Vec3i{dx, dy, dz})) > 0; // TODO: Adjust for arbitrary voxel models.
const fullDx = dx - @as(i32, @intFromFloat(normalVector[0])); // TODO: This calculation should only be done for border faces.
const fullDy = dy - @as(i32, @intFromFloat(normalVector[1]));
const fullDz = dz - @as(i32, @intFromFloat(normalVector[2]));
self.distance = @abs(fullDx) + @abs(fullDy) + @abs(fullDz);
}
};
pos: chunk.ChunkPosition,
size: i32,
chunk: *chunk.Chunk,
lightingData: [2]*lighting.ChannelChunk,
opaqueMesh: PrimitiveMesh,
transparentMesh: PrimitiveMesh,
lightList: []u32 = &.{},
lightListNeedsUpload: bool = false,
lightAllocation: graphics.SubAllocation = .{.start = 0, .len = 0},
lastNeighborsSameLod: [6]?*const ChunkMesh = @splat(null),
lastNeighborsHigherLod: [6]?*const ChunkMesh = @splat(null),
isNeighborLod: [6]bool = @splat(false),
currentSorting: []SortingData = &.{},
sortingOutputBuffer: []FaceData = &.{},
culledSortingCount: u31 = 0,
lastTransparentUpdatePos: Vec3i = Vec3i{0, 0, 0},
refCount: std.atomic.Value(u32) = .init(1),
needsLightRefresh: std.atomic.Value(bool) = .init(false),
needsMeshUpdate: bool = false,
finishedMeshing: bool = false, // Must be synced with node.finishedMeshing in mesh_storage.zig
finishedLighting: bool = false,
litNeighbors: Atomic(u32) = .init(0),
mutex: std.Thread.Mutex = .{},
chunkAllocation: graphics.SubAllocation = .{.start = 0, .len = 0},
min: Vec3f = undefined,
max: Vec3f = undefined,
blockBreakingFaces: main.List(FaceData),
blockBreakingFacesSortingData: []SortingData = &.{},
blockBreakingFacesChanged: bool = false,
pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) void {
self.* = ChunkMesh{
.pos = pos,
.size = chunk.chunkSize*pos.voxelSize,
.opaqueMesh = .{},
.transparentMesh = .{},
.chunk = ch,
.lightingData = .{
lighting.ChannelChunk.init(ch, false),
lighting.ChannelChunk.init(ch, true),
},
.blockBreakingFaces = .init(main.globalAllocator),
};
}
pub fn deinit(self: *ChunkMesh) void {
std.debug.assert(self.refCount.load(.monotonic) == 0);
chunkBuffer.free(self.chunkAllocation);
self.opaqueMesh.deinit();
self.transparentMesh.deinit();
self.chunk.deinit();
main.globalAllocator.free(self.currentSorting);
main.globalAllocator.free(self.sortingOutputBuffer);
for(self.lightingData) |lightingChunk| {
lightingChunk.deinit();
}
self.blockBreakingFaces.deinit();
main.globalAllocator.free(self.blockBreakingFacesSortingData);
main.globalAllocator.free(self.lightList);
lightBuffer.free(self.lightAllocation);
}
pub fn increaseRefCount(self: *ChunkMesh) void {
const prevVal = self.refCount.fetchAdd(1, .monotonic);
std.debug.assert(prevVal != 0);
}
/// In cases where it's not certain whether the thing was cleared already.
pub fn tryIncreaseRefCount(self: *ChunkMesh) bool {
var prevVal = self.refCount.load(.monotonic);
while(prevVal != 0) {
prevVal = self.refCount.cmpxchgWeak(prevVal, prevVal + 1, .monotonic, .monotonic) orelse return true;
}
return false;
}
pub fn decreaseRefCount(self: *ChunkMesh) void {
const prevVal = self.refCount.fetchSub(1, .monotonic);
std.debug.assert(prevVal != 0);
if(prevVal == 1) {
mesh_storage.addMeshToClearListAndDecreaseRefCount(self);
}
}
pub fn scheduleLightRefreshAndDecreaseRefCount1(self: *ChunkMesh) void {
LightRefreshTask.scheduleAndDecreaseRefCount(self);
}
const LightRefreshTask = struct {
mesh: *ChunkMesh,
pub const vtable = main.utils.ThreadPool.VTable{
.getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority),
.isStillNeeded = main.utils.castFunctionSelfToAnyopaque(isStillNeeded),
.run = main.utils.castFunctionSelfToAnyopaque(run),
.clean = main.utils.castFunctionSelfToAnyopaque(clean),
.taskType = .misc,
};
pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) void {
const task = main.globalAllocator.create(LightRefreshTask);
task.* = .{
.mesh = mesh,
};
main.threadPool.addTask(task, &vtable);
}
pub fn getPriority(_: *LightRefreshTask) f32 {
return 1000000;
}
pub fn isStillNeeded(_: *LightRefreshTask) bool {
return true; // TODO: Is it worth checking for this?
}
pub fn run(self: *LightRefreshTask) void {
if(self.mesh.needsLightRefresh.swap(false, .acq_rel)) {
self.mesh.mutex.lock();
self.mesh.finishData();
self.mesh.mutex.unlock();
mesh_storage.addToUpdateListAndDecreaseRefCount(self.mesh);
} else {
self.mesh.decreaseRefCount();
}
main.globalAllocator.destroy(self);
}
pub fn clean(self: *LightRefreshTask) void {
self.mesh.decreaseRefCount();
main.globalAllocator.destroy(self);
}
};
pub fn isEmpty(self: *const ChunkMesh) bool {
return self.opaqueMesh.vertexCount == 0 and self.transparentMesh.vertexCount == 0;
}
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: chunk.Neighbor) bool {
const rotatedModel = blocks.meshes.model(block).model();
_ = rotatedModel; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant)
return block.typ != 0 and (other.typ == 0 or (block != other and other.viewThrough()) or other.alwaysViewThrough() or !blocks.meshes.model(other).model().isNeighborOccluded[neighbor.reverse().toInt()]);
}
fn initLight(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void {
self.mutex.lock();
var lightEmittingBlocks = main.List([3]u8).init(main.stackAllocator);
defer lightEmittingBlocks.deinit();
var x: u8 = 0;
while(x < chunk.chunkSize) : (x += 1) {
var y: u8 = 0;
while(y < chunk.chunkSize) : (y += 1) {
var z: u8 = 0;
while(z < chunk.chunkSize) : (z += 1) {
const block = self.chunk.data.getValue(chunk.getIndex(x, y, z));
if(block.light() != 0) lightEmittingBlocks.append(.{x, y, z});
}
}
}
self.mutex.unlock();
self.lightingData[0].propagateLights(lightEmittingBlocks.items, true, lightRefreshList);
sunLight: {
var allSun: bool = self.chunk.data.paletteLength == 1 and self.chunk.data.palette[0].typ == 0;
var sunStarters: [chunk.chunkSize*chunk.chunkSize][3]u8 = undefined;
var index: usize = 0;
const lightStartMap = mesh_storage.getLightMapPieceAndIncreaseRefCount(self.pos.wx, self.pos.wy, self.pos.voxelSize) orelse break :sunLight;
defer lightStartMap.decreaseRefCount();
x = 0;
while(x < chunk.chunkSize) : (x += 1) {
var y: u8 = 0;
while(y < chunk.chunkSize) : (y += 1) {
const startHeight: i32 = lightStartMap.getHeight(self.pos.wx + x*self.pos.voxelSize, self.pos.wy + y*self.pos.voxelSize);
const relHeight = startHeight -% self.pos.wz;
if(relHeight < chunk.chunkSize*self.pos.voxelSize) {
sunStarters[index] = .{x, y, chunk.chunkSize - 1};
index += 1;
} else {
allSun = false;
}
}
}
if(allSun) {
self.lightingData[1].propagateUniformSun(lightRefreshList);
} else {
self.lightingData[1].propagateLights(sunStarters[0..index], true, lightRefreshList);
}
}
}
pub fn generateLightingData(self: *ChunkMesh) error{AlreadyStored}!void {
try mesh_storage.addMeshToStorage(self);
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer lightRefreshList.deinit();
self.initLight(&lightRefreshList);
self.mutex.lock();
self.finishedLighting = true;
self.mutex.unlock();
// Only generate a mesh if the surrounding 27 chunks finished the light generation steps.
var dx: i32 = -1;
while(dx <= 1) : (dx += 1) {
var dy: i32 = -1;
while(dy <= 1) : (dy += 1) {
var dz: i32 = -1;
while(dz <= 1) : (dz += 1) {
var pos = self.pos;
pos.wx +%= pos.voxelSize*chunk.chunkSize*dx;
pos.wy +%= pos.voxelSize*chunk.chunkSize*dy;
pos.wz +%= pos.voxelSize*chunk.chunkSize*dz;
const neighborMesh = mesh_storage.getMeshAndIncreaseRefCount(pos) orelse continue;
defer neighborMesh.decreaseRefCount();
const shiftSelf: u5 = @intCast(((dx + 1)*3 + dy + 1)*3 + dz + 1);
const shiftOther: u5 = @intCast(((-dx + 1)*3 + -dy + 1)*3 + -dz + 1);
if(neighborMesh.litNeighbors.fetchOr(@as(u27, 1) << shiftOther, .monotonic) ^ @as(u27, 1) << shiftOther == ~@as(u27, 0)) { // Trigger mesh creation for neighbor
neighborMesh.generateMesh(&lightRefreshList);
}
neighborMesh.mutex.lock();
const neighborFinishedLighting = neighborMesh.finishedLighting;
neighborMesh.mutex.unlock();
if(neighborFinishedLighting and self.litNeighbors.fetchOr(@as(u27, 1) << shiftSelf, .monotonic) ^ @as(u27, 1) << shiftSelf == ~@as(u27, 0)) {
self.generateMesh(&lightRefreshList);
}
}
}
}
for(lightRefreshList.items) |other| {
if(other.needsLightRefresh.load(.unordered)) {
other.scheduleLightRefreshAndDecreaseRefCount1();
} else {
other.decreaseRefCount();
}
}
}
fn appendInternalQuads(block: Block, x: i32, y: i32, z: i32, comptime backFace: bool, list: *main.ListUnmanaged(FaceData), allocator: main.heap.NeverFailingAllocator) void {
const model = blocks.meshes.model(block).model();
model.appendInternalQuadsToList(list, allocator, block, x, y, z, backFace);
}
fn appendNeighborFacingQuads(block: Block, neighbor: chunk.Neighbor, x: i32, y: i32, z: i32, comptime backFace: bool, list: *main.ListUnmanaged(FaceData), allocator: main.heap.NeverFailingAllocator) void {
const model = blocks.meshes.model(block).model();
model.appendNeighborFacingQuadsToList(list, allocator, block, neighbor, x, y, z, backFace);
}
pub fn generateMesh(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void {
var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&alwaysViewThroughMask), 0);
var alwaysViewThroughMask2: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
var canSeeNeighbor: [6][chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&canSeeNeighbor), 0);
var canSeeAllNeighbors: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&canSeeAllNeighbors), 0);
var hasFaces: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&hasFaces), 0);
self.mutex.lock();
var transparentCore: main.ListUnmanaged(FaceData) = .{};
defer transparentCore.deinit(main.stackAllocator);
var opaqueCore: main.ListUnmanaged(FaceData) = .{};
defer opaqueCore.deinit(main.stackAllocator);
var transparentOptional: main.ListUnmanaged(FaceData) = .{};
defer transparentOptional.deinit(main.stackAllocator);
var opaqueOptional: main.ListUnmanaged(FaceData) = .{};
defer opaqueOptional.deinit(main.stackAllocator);
const OcclusionInfo = packed struct {
canSeeNeighbor: u6 = 0,
canSeeAllNeighbors: bool = false,
hasExternalQuads: bool = false,
hasInternalQuads: bool = false,
alwaysViewThrough: bool = false,
};
var paletteCache = main.stackAllocator.alloc(OcclusionInfo, self.chunk.data.paletteLength);
defer main.stackAllocator.free(paletteCache);
for(0..self.chunk.data.paletteLength) |i| {
const block = self.chunk.data.palette[i];
const model = blocks.meshes.model(block).model();
var result: OcclusionInfo = .{};
if(model.noNeighborsOccluded or block.viewThrough()) {
result.canSeeAllNeighbors = true;
} else if(!model.allNeighborsOccluded) {
for(chunk.Neighbor.iterable) |neighbor| {
if(!model.isNeighborOccluded[neighbor.toInt()]) {
result.canSeeNeighbor |= neighbor.bitMask();
}
}
}
if(model.hasNeighborFacingQuads) {
result.hasExternalQuads = true;
}
if(model.internalQuads.len != 0) {
result.hasInternalQuads = true;
}
result.alwaysViewThrough = block.alwaysViewThrough() and block.opaqueVariant() != block.typ;
paletteCache[i] = result;
}
// Generate the bitMasks:
for(0..chunk.chunkSize) |_x| {
const x: u5 = @intCast(_x);
for(0..chunk.chunkSize) |_y| {
const y: u5 = @intCast(_y);
for(0..chunk.chunkSize) |_z| {
const z: u5 = @intCast(_z);
const paletteId = self.chunk.data.data.getValue(chunk.getIndex(x, y, z));
const occlusionInfo = paletteCache[paletteId];
const setBit = @as(u32, 1) << z;
if(occlusionInfo.alwaysViewThrough or (!occlusionInfo.canSeeAllNeighbors and occlusionInfo.canSeeNeighbor == 0)) {
alwaysViewThroughMask[x][y] |= setBit;
}
}
}
}
const initialAlwaysViewThroughMask = alwaysViewThroughMask;
const depthFilteredViewThroughMask = blk: {
var a = &alwaysViewThroughMask;
var b = &alwaysViewThroughMask2;
for(0..main.settings.leavesQuality) |_| {
for(0..chunk.chunkSize) |_x| {
const x: u5 = @intCast(_x);
for(0..chunk.chunkSize) |_y| {
const y: u5 = @intCast(_y);
var mask = a[x][y];
mask &= mask << 1;
mask &= mask >> 1;
if(x == 0) mask = 0 else mask &= a[x - 1][y];
if(x == chunk.chunkSize - 1) mask = 0 else mask &= a[x + 1][y];
if(y == 0) mask = 0 else mask &= a[x][y - 1];
if(y == chunk.chunkSize - 1) mask = 0 else mask &= a[x][y + 1];
b[x][y] = mask;
}
}
const swap = a;
a = b;
b = swap;
}
break :blk a;
};
for(0..chunk.chunkSize) |_x| {
const x: u5 = @intCast(_x);
for(0..chunk.chunkSize) |_y| {
const y: u5 = @intCast(_y);
for(0..chunk.chunkSize) |_z| {
const z: u5 = @intCast(_z);
const paletteId = self.chunk.data.data.getValue(chunk.getIndex(x, y, z));
const occlusionInfo = paletteCache[paletteId];
const setBit = @as(u32, 1) << z;
if(depthFilteredViewThroughMask[x][y] & setBit != 0) {} else if(occlusionInfo.canSeeAllNeighbors) {
canSeeAllNeighbors[x][y] |= setBit;
} else if(occlusionInfo.canSeeNeighbor != 0) {
for(chunk.Neighbor.iterable) |neighbor| {
if(occlusionInfo.canSeeNeighbor & neighbor.bitMask() != 0) {
canSeeNeighbor[neighbor.toInt()][x][y] |= setBit;
}
}
}
if(occlusionInfo.hasExternalQuads) {
hasFaces[x][y] |= setBit;
}
if(occlusionInfo.hasInternalQuads) {
const block = self.chunk.data.palette[paletteId];
if(block.transparent()) {
appendInternalQuads(block, x, y, z, false, &transparentCore, main.stackAllocator);
} else {
appendInternalQuads(block, x, y, z, false, &opaqueCore, main.stackAllocator);
}
}
}
}
}
// Generate the meshes:
{
const neighbor = chunk.Neighbor.dirNegX;
for(1..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x - 1][y] | canSeeAllNeighbors[x - 1][y]);
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x - 1), @intCast(y), z));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x - 1), @intCast(y), z, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x - 1), @intCast(y), z, false, if(initialAlwaysViewThroughMask[x - 1][y] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
{
const neighbor = chunk.Neighbor.dirPosX;
for(0..chunk.chunkSize - 1) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x + 1][y] | canSeeAllNeighbors[x + 1][y]);
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x + 1), @intCast(y), z));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x + 1), @intCast(y), z, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x + 1), @intCast(y), z, false, if(initialAlwaysViewThroughMask[x + 1][y] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
{
const neighbor = chunk.Neighbor.dirNegY;
for(0..chunk.chunkSize) |x| {
for(1..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x][y - 1] | canSeeAllNeighbors[x][y - 1]);
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y - 1), z));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y - 1), z, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y - 1), z, false, if(initialAlwaysViewThroughMask[x][y - 1] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
{
const neighbor = chunk.Neighbor.dirPosY;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize - 1) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x][y + 1] | canSeeAllNeighbors[x][y + 1]);
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y + 1), z));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y + 1), z, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y + 1), z, false, if(initialAlwaysViewThroughMask[x][y + 1] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
{
const neighbor = chunk.Neighbor.dirDown;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x][y] | canSeeAllNeighbors[x][y]) << 1;
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z - 1));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z - 1, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z - 1, false, if(initialAlwaysViewThroughMask[x][y] << 1 & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
{
const neighbor = chunk.Neighbor.dirUp;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[comptime neighbor.reverse().toInt()][x][y] | canSeeAllNeighbors[x][y]) >> 1;
while(bitMask != 0) {
const z = @ctz(bitMask);
const setBit = @as(u32, 1) << @intCast(z);
bitMask &= ~setBit;
var block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(depthFilteredViewThroughMask[x][y] & setBit != 0) block.typ = block.opaqueVariant();
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z + 1));
if(block == neighborBlock) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z + 1, false, &transparentCore, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z + 1, false, if(initialAlwaysViewThroughMask[x][y] >> 1 & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
}
}
}
}
}
self.mutex.unlock();
self.opaqueMesh.replaceRange(.core, opaqueCore.items);
self.opaqueMesh.replaceRange(.optional, opaqueOptional.items);
self.transparentMesh.replaceRange(.core, transparentCore.items);
self.transparentMesh.replaceRange(.optional, transparentOptional.items);
self.finishNeighbors(lightRefreshList);
}
fn updateBlockLight(self: *ChunkMesh, x: u5, y: u5, z: u5, newBlock: Block, lightRefreshList: *main.List(*ChunkMesh)) void {
for(self.lightingData[0..]) |lightingData| {
lightingData.propagateLightsDestructive(&.{.{x, y, z}}, lightRefreshList);
}
if(newBlock.light() != 0) {
self.lightingData[0].propagateLights(&.{.{x, y, z}}, false, lightRefreshList);
}
}
pub fn updateBlock(self: *ChunkMesh, _x: i32, _y: i32, _z: i32, _newBlock: Block) void {
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
defer lightRefreshList.deinit();
const x: u5 = @intCast(_x & chunk.chunkMask);
const y: u5 = @intCast(_y & chunk.chunkMask);
const z: u5 = @intCast(_z & chunk.chunkMask);
var newBlock = _newBlock;
self.mutex.lock();
if(self.chunk.data.getValue(chunk.getIndex(x, y, z)) == newBlock) {
self.mutex.unlock();
return;
}
self.mutex.unlock();
var neighborBlocks: [6]Block = undefined;
@memset(&neighborBlocks, .{.typ = 0, .data = 0});
for(chunk.Neighbor.iterable) |neighbor| {
const nx = x + neighbor.relX();
const ny = y + neighbor.relY();
const nz = z + neighbor.relZ();
if(nx & chunk.chunkMask != nx or ny & chunk.chunkMask != ny or nz & chunk.chunkMask != nz) {
const neighborChunkMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, self.pos.voxelSize, neighbor) orelse continue;
defer neighborChunkMesh.decreaseRefCount();
const index = chunk.getIndex(nx & chunk.chunkMask, ny & chunk.chunkMask, nz & chunk.chunkMask);
neighborChunkMesh.mutex.lock();
var neighborBlock = neighborChunkMesh.chunk.data.getValue(index);
if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
neighborChunkMesh.chunk.data.setValue(index, neighborBlock);
neighborChunkMesh.mutex.unlock();
neighborChunkMesh.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
neighborChunkMesh.generateMesh(&lightRefreshList);
neighborChunkMesh.mutex.lock();
}
}
neighborChunkMesh.mutex.unlock();
neighborBlocks[neighbor.toInt()] = neighborBlock;
} else {
const index = chunk.getIndex(nx, ny, nz);
self.mutex.lock();
var neighborBlock = self.chunk.data.getValue(index);
if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
self.chunk.data.setValue(index, neighborBlock);
self.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
}
}
self.mutex.unlock();
neighborBlocks[neighbor.toInt()] = neighborBlock;
}
}
if(newBlock.mode().dependsOnNeighbors) {
for(chunk.Neighbor.iterable) |neighbor| {
_ = newBlock.mode().updateData(&newBlock, neighbor, neighborBlocks[neighbor.toInt()]);
}
}
self.mutex.lock();
self.chunk.data.setValue(chunk.getIndex(x, y, z), newBlock);
self.mutex.unlock();
self.updateBlockLight(x, y, z, newBlock, &lightRefreshList);
self.mutex.lock();
defer self.mutex.unlock();
// Update neighbor chunks:
if(x == 0) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirNegX.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirNegX.toInt()] = null;
} else if(x == 31) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirPosX.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirPosX.toInt()] = null;
}
if(y == 0) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirNegY.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirNegY.toInt()] = null;
} else if(y == 31) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirPosY.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirPosY.toInt()] = null;
}
if(z == 0) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirDown.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirDown.toInt()] = null;
} else if(z == 31) {
self.lastNeighborsHigherLod[chunk.Neighbor.dirUp.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirUp.toInt()] = null;
}
self.mutex.unlock();
self.generateMesh(&lightRefreshList); // TODO: Batch mesh updates instead of applying them for each block changes.
self.mutex.lock();
for(lightRefreshList.items) |other| {
if(other.needsLightRefresh.load(.unordered)) {
other.scheduleLightRefreshAndDecreaseRefCount1();
} else {
other.decreaseRefCount();
}
}
self.uploadData();
}
fn clearNeighborA(self: *ChunkMesh, neighbor: chunk.Neighbor, comptime isLod: bool) void {
self.opaqueMesh.clearNeighbor(neighbor, isLod);
self.transparentMesh.clearNeighbor(neighbor, isLod);
}
pub fn finishData(self: *ChunkMesh) void {
main.utils.assertLocked(&self.mutex);
var lightList = main.List(u32).init(main.stackAllocator);
defer lightList.deinit();
var lightMap = std.AutoHashMap([4]u32, u16).init(main.stackAllocator.allocator);
defer lightMap.deinit();
self.opaqueMesh.finish(self, &lightList, &lightMap);
self.transparentMesh.finish(self, &lightList, &lightMap);
self.lightList = main.globalAllocator.realloc(self.lightList, lightList.items.len);
@memcpy(self.lightList, lightList.items);
self.lightListNeedsUpload = true;
self.min = @min(self.opaqueMesh.min, self.transparentMesh.min);
self.max = @max(self.opaqueMesh.max, self.transparentMesh.max);
}
pub fn uploadData(self: *ChunkMesh) void {
self.opaqueMesh.uploadData(self.isNeighborLod);
self.transparentMesh.uploadData(self.isNeighborLod);
if(self.lightListNeedsUpload) {
self.lightListNeedsUpload = false;
lightBuffer.uploadData(self.lightList, &self.lightAllocation);
}
self.uploadChunkPosition();
}
fn deadlockFreeDoubleLock(m1: *std.Thread.Mutex, m2: *std.Thread.Mutex) void {
if(@intFromPtr(m1) < @intFromPtr(m2)) {
m1.lock();
m2.lock();
} else {
m2.lock();
m1.lock();
}
}
fn finishNeighbors(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void {
for(chunk.Neighbor.iterable) |neighbor| {
const nullNeighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, self.pos.voxelSize, neighbor);
if(nullNeighborMesh) |neighborMesh| sameLodBlock: {
defer neighborMesh.decreaseRefCount();
std.debug.assert(neighborMesh != self);
deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex);
defer self.mutex.unlock();
defer neighborMesh.mutex.unlock();
if(self.lastNeighborsSameLod[neighbor.toInt()] == neighborMesh) break :sameLodBlock;
self.lastNeighborsSameLod[neighbor.toInt()] = neighborMesh;
neighborMesh.lastNeighborsSameLod[neighbor.reverse().toInt()] = self;
var transparentSelf: main.ListUnmanaged(FaceData) = .{};
defer transparentSelf.deinit(main.stackAllocator);
var opaqueSelf: main.ListUnmanaged(FaceData) = .{};
defer opaqueSelf.deinit(main.stackAllocator);
var transparentNeighbor: main.ListUnmanaged(FaceData) = .{};
defer transparentNeighbor.deinit(main.stackAllocator);
var opaqueNeighbor: main.ListUnmanaged(FaceData) = .{};
defer opaqueNeighbor.deinit(main.stackAllocator);
const x3: i32 = if(neighbor.isPositive()) chunk.chunkMask else 0;
var x1: i32 = 0;
while(x1 < chunk.chunkSize) : (x1 += 1) {
var x2: i32 = 0;
while(x2 < chunk.chunkSize) : (x2 += 1) {
var x: i32 = undefined;
var y: i32 = undefined;
var z: i32 = undefined;
if(neighbor.relX() != 0) {
x = x3;
y = x1;
z = x2;
} else if(neighbor.relY() != 0) {
x = x1;
y = x3;
z = x2;
} else {
x = x2;
y = x1;
z = x3;
}
const otherX = x +% neighbor.relX() & chunk.chunkMask;
const otherY = y +% neighbor.relY() & chunk.chunkMask;
const otherZ = z +% neighbor.relZ() & chunk.chunkMask;
var block = self.chunk.data.getValue(chunk.getIndex(x, y, z));
if(settings.leavesQuality == 0) block.typ = block.opaqueVariant();
var otherBlock = neighborMesh.chunk.data.getValue(chunk.getIndex(otherX, otherY, otherZ));
if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant();
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
if(block.transparent()) {
if(block.hasBackFace()) {
appendNeighborFacingQuads(block, neighbor.reverse(), x, y, z, true, &transparentSelf, main.stackAllocator);
}
appendNeighborFacingQuads(block, neighbor, otherX, otherY, otherZ, false, &transparentNeighbor, main.stackAllocator);
} else {
appendNeighborFacingQuads(block, neighbor, otherX, otherY, otherZ, false, &opaqueNeighbor, main.stackAllocator);
}
}
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) {
if(otherBlock.transparent()) {
if(otherBlock.hasBackFace()) {
appendNeighborFacingQuads(otherBlock, neighbor, otherX, otherY, otherZ, true, &transparentNeighbor, main.stackAllocator);
}
appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &transparentSelf, main.stackAllocator);
} else {
appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &opaqueSelf, main.stackAllocator);
}
}
}
}
self.opaqueMesh.replaceRange(.neighbor(neighbor), opaqueSelf.items);
self.transparentMesh.replaceRange(.neighbor(neighbor), transparentSelf.items);
neighborMesh.opaqueMesh.replaceRange(.neighbor(neighbor.reverse()), opaqueNeighbor.items);
neighborMesh.transparentMesh.replaceRange(.neighbor(neighbor.reverse()), transparentNeighbor.items);
_ = neighborMesh.needsLightRefresh.store(true, .release);
neighborMesh.increaseRefCount();
lightRefreshList.append(neighborMesh);
} else {
self.mutex.lock();
defer self.mutex.unlock();
if(self.lastNeighborsSameLod[neighbor.toInt()] != null) {
self.opaqueMesh.replaceRange(.neighbor(neighbor), &.{});
self.transparentMesh.replaceRange(.neighbor(neighbor), &.{});
self.lastNeighborsSameLod[neighbor.toInt()] = null;
}
}
// lod border:
if(self.pos.voxelSize == @as(u31, 1) << settings.highestLod) continue;
const neighborMesh = mesh_storage.getNeighborAndIncreaseRefCount(self.pos, 2*self.pos.voxelSize, neighbor) orelse {
self.mutex.lock();
defer self.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) {
self.opaqueMesh.replaceRange(.neighborLod(neighbor), &.{});
self.transparentMesh.replaceRange(.neighborLod(neighbor), &.{});
self.lastNeighborsHigherLod[neighbor.toInt()] = null;
}
continue;
};
defer neighborMesh.decreaseRefCount();
deadlockFreeDoubleLock(&self.mutex, &neighborMesh.mutex);
defer self.mutex.unlock();
defer neighborMesh.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] == neighborMesh) continue;
self.lastNeighborsHigherLod[neighbor.toInt()] = neighborMesh;
var transparentSelf: main.ListUnmanaged(FaceData) = .{};
defer transparentSelf.deinit(main.stackAllocator);
var opaqueSelf: main.ListUnmanaged(FaceData) = .{};
defer opaqueSelf.deinit(main.stackAllocator);
const x3: i32 = if(neighbor.isPositive()) chunk.chunkMask else 0;
const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunk.chunkSize;
const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunk.chunkSize;
const offsetZ = @divExact(self.pos.wz, self.pos.voxelSize) & chunk.chunkSize;
var x1: i32 = 0;
while(x1 < chunk.chunkSize) : (x1 += 1) {
var x2: i32 = 0;
while(x2 < chunk.chunkSize) : (x2 += 1) {
var x: i32 = undefined;
var y: i32 = undefined;
var z: i32 = undefined;
if(neighbor.relX() != 0) {
x = x3;
y = x1;
z = x2;
} else if(neighbor.relY() != 0) {
x = x1;
y = x3;
z = x2;
} else {
x = x2;
y = x1;
z = x3;
}
const otherX = (x +% neighbor.relX() +% offsetX >> 1) & chunk.chunkMask;
const otherY = (y +% neighbor.relY() +% offsetY >> 1) & chunk.chunkMask;
const otherZ = (z +% neighbor.relZ() +% offsetZ >> 1) & chunk.chunkMask;
var block = self.chunk.data.getValue(chunk.getIndex(x, y, z));
if(settings.leavesQuality == 0) block.typ = block.opaqueVariant();
var otherBlock = neighborMesh.chunk.data.getValue(chunk.getIndex(otherX, otherY, otherZ));
if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant();
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) {
if(otherBlock.transparent()) {
appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &transparentSelf, main.stackAllocator);
} else {
appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &opaqueSelf, main.stackAllocator);
}
}
if(block.hasBackFace()) {
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
appendNeighborFacingQuads(block, neighbor.reverse(), x, y, z, true, &transparentSelf, main.stackAllocator);
}
}
}
}
self.opaqueMesh.replaceRange(.neighborLod(neighbor), opaqueSelf.items);
self.transparentMesh.replaceRange(.neighborLod(neighbor), transparentSelf.items);
}
self.mutex.lock();
defer self.mutex.unlock();
_ = self.needsLightRefresh.swap(false, .acq_rel);
self.finishData();
mesh_storage.finishMesh(self);
}
fn uploadChunkPosition(self: *ChunkMesh) void {
chunkBuffer.uploadData(&.{ChunkData{
.position = .{self.pos.wx, self.pos.wy, self.pos.wz},
.voxelSize = self.pos.voxelSize,
.lightStart = self.lightAllocation.start,
.vertexStartOpaque = self.opaqueMesh.bufferAllocation.start*4,
.faceCountsByNormalOpaque = self.opaqueMesh.byNormalCount,
.vertexStartTransparent = self.transparentMesh.bufferAllocation.start*4,
.vertexCountTransparent = self.transparentMesh.bufferAllocation.len*6,
.min = self.min,
.max = self.max,
.visibilityState = 0,
.oldVisibilityState = 0,
}}, &self.chunkAllocation);
}
pub fn prepareRendering(self: *ChunkMesh, chunkList: *main.List(u32)) void {
if(self.opaqueMesh.vertexCount == 0) return;
chunkList.append(self.chunkAllocation.start);
quadsDrawn += self.opaqueMesh.vertexCount/6;
}
pub fn prepareTransparentRendering(self: *ChunkMesh, playerPosition: Vec3d, chunkList: *main.List(u32)) void {
if(self.transparentMesh.vertexCount == 0 and self.blockBreakingFaces.items.len == 0) return;
var needsUpdate: bool = false;
if(self.transparentMesh.wasChanged) {
self.transparentMesh.wasChanged = false;
self.transparentMesh.lock.lockRead();
defer self.transparentMesh.lock.unlockRead();
var len: usize = 0;
const coreList = self.transparentMesh.completeList.getRange(.core);
len += coreList.len;
var list: [6][]FaceData = undefined;
for(0..6) |i| {
if(!self.isNeighborLod[i]) {
list[i] = self.transparentMesh.completeList.getRange(.neighbor(@enumFromInt(i)));
} else {
list[i] = self.transparentMesh.completeList.getRange(.neighborLod(@enumFromInt(i)));
}
len += list[i].len;
}
self.currentSorting = main.globalAllocator.realloc(self.currentSorting, len);
self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, len + self.blockBreakingFaces.items.len);
for(0..coreList.len) |i| {
self.currentSorting[i].face = coreList[i];
}
var offset = coreList.len;
for(0..6) |n| {
for(0..list[n].len) |i| {
self.currentSorting[offset + i].face = list[n][i];
}
offset += list[n].len;
}
needsUpdate = true;
}
var relativePos = Vec3d{
@as(f64, @floatFromInt(self.pos.wx)) - playerPosition[0],
@as(f64, @floatFromInt(self.pos.wy)) - playerPosition[1],
@as(f64, @floatFromInt(self.pos.wz)) - playerPosition[2],
}/@as(Vec3d, @splat(@as(f64, @floatFromInt(self.pos.voxelSize))));
relativePos = @min(relativePos, @as(Vec3d, @splat(0)));
relativePos = @max(relativePos, @as(Vec3d, @splat(-32)));
const updatePos: Vec3i = @intFromFloat(relativePos);
if(@reduce(.Or, updatePos != self.lastTransparentUpdatePos)) {
self.lastTransparentUpdatePos = updatePos;
needsUpdate = true;
}
if(self.blockBreakingFacesChanged) {
self.blockBreakingFacesChanged = false;
self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, self.currentSorting.len + self.blockBreakingFaces.items.len);
self.blockBreakingFacesSortingData = main.globalAllocator.realloc(self.blockBreakingFacesSortingData, self.blockBreakingFaces.items.len);
for(0..self.blockBreakingFaces.items.len) |i| {
self.blockBreakingFacesSortingData[i].face = self.blockBreakingFaces.items[i];
}
needsUpdate = true;
}
if(needsUpdate) {
for(self.currentSorting) |*val| {
val.update(
updatePos[0],
updatePos[1],
updatePos[2],
);
}
for(0..self.blockBreakingFaces.items.len) |i| {
self.blockBreakingFacesSortingData[i].update(updatePos[0], updatePos[1], updatePos[2]);
}
// Sort by back vs front face:
var backFaceStart: usize = 0;
{
var i: usize = 0;
var culledStart: usize = self.currentSorting.len;
while(culledStart > 0) {
if(!self.currentSorting[culledStart - 1].shouldBeCulled) {
break;
}
culledStart -= 1;
}
while(i < culledStart) : (i += 1) {
if(self.currentSorting[i].shouldBeCulled) {
culledStart -= 1;
std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[culledStart]);
while(culledStart > 0) {
if(!self.currentSorting[culledStart - 1].shouldBeCulled) {
break;
}
culledStart -= 1;
}
}
if(!self.currentSorting[i].isBackFace) {
std.mem.swap(SortingData, &self.currentSorting[i], &self.currentSorting[backFaceStart]);
backFaceStart += 1;
}
}
self.culledSortingCount = @intCast(culledStart);
}
// Sort it using bucket sort:
var buckets: [34*3]u32 = undefined;
@memset(&buckets, 0);
for(self.blockBreakingFacesSortingData) |val| {
buckets[34*3 - 1 - val.distance] += 1;
}
for(self.currentSorting[0..self.culledSortingCount]) |val| {
buckets[34*3 - 1 - val.distance] += 1;
}
var prefixSum: u32 = 0;
for(&buckets) |*val| {
const copy = val.*;
val.* = prefixSum;
prefixSum += copy;
}
// Move it over into a new buffer:
for(0..backFaceStart) |i| {
const bucket = 34*3 - 1 - self.currentSorting[i].distance;
self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face;
buckets[bucket] += 1;
}
// Block breaking faces should be drawn after front faces, but before the corresponding backfaces.
for(self.blockBreakingFacesSortingData) |val| {
const bucket = 34*3 - 1 - val.distance;
self.sortingOutputBuffer[buckets[bucket]] = val.face;
buckets[bucket] += 1;
}
for(backFaceStart..self.culledSortingCount) |i| {
const bucket = 34*3 - 1 - self.currentSorting[i].distance;
self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face;
buckets[bucket] += 1;
}
self.culledSortingCount += @intCast(self.blockBreakingFaces.items.len);
// Upload:
faceBuffer.uploadData(self.sortingOutputBuffer[0..self.culledSortingCount], &self.transparentMesh.bufferAllocation);
self.uploadChunkPosition();
}
chunkList.append(self.chunkAllocation.start);
transparentQuadsDrawn += self.culledSortingCount;
}
};