Improve how chunk meshes store their mesh data to avoid needless copies and needless allocations.

Instead of storing 14 different array lists, the parts of the mesh are now constructed on the stack and then directly inserted into the full list (which I abstracted into a generic data structure)

fixes #1188

makes #182 less severe (it is now overshadowed by #1202)
This commit is contained in:
IntegratedQuantum 2025-03-13 21:52:47 +01:00
parent 32269f1f84
commit e7ecd161ce
4 changed files with 224 additions and 181 deletions

View File

@ -31,6 +31,7 @@ pub const heap = @import("utils/heap.zig");
pub const List = @import("utils/list.zig").List; pub const List = @import("utils/list.zig").List;
pub const ListUnmanaged = @import("utils/list.zig").ListUnmanaged; pub const ListUnmanaged = @import("utils/list.zig").ListUnmanaged;
pub const MultiArray = @import("utils/list.zig").MultiArray;
pub const VirtualList = @import("utils/list.zig").VirtualList; pub const VirtualList = @import("utils/list.zig").VirtualList;
const file_monitor = utils.file_monitor; const file_monitor = utils.file_monitor;

View File

@ -286,16 +286,32 @@ pub const IndirectData = extern struct {
}; };
const PrimitiveMesh = struct { // MARK: PrimitiveMesh const PrimitiveMesh = struct { // MARK: PrimitiveMesh
coreFaces: main.ListUnmanaged(FaceData) = .{}, const FaceGroups = enum(u32) {
neighborFacesSameLod: [6]main.ListUnmanaged(FaceData) = @splat(.{}), core,
neighborFacesHigherLod: [6]main.ListUnmanaged(FaceData) = @splat(.{}), neighbor0,
optionalFaces: main.ListUnmanaged(FaceData) = .{}, neighbor1,
completeList: []FaceData = &.{}, neighbor2,
coreLen: u32 = 0, neighbor3,
sameLodLens: [6]u32 = @splat(0), neighbor4,
higherLodLens: [6]u32 = @splat(0), neighbor5,
optionalLen: u32 = 0, neighborLod0,
mutex: std.Thread.Mutex = .{}, 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}, bufferAllocation: graphics.SubAllocation = .{.start = 0, .len = 0},
vertexCount: u31 = 0, vertexCount: u31 = 0,
byNormalCount: [14]u32 = @splat(0), byNormalCount: [14]u32 = @splat(0),
@ -304,86 +320,23 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh
max: Vec3f = undefined, max: Vec3f = undefined,
fn deinit(self: *PrimitiveMesh) void { fn deinit(self: *PrimitiveMesh) void {
faceBuffer.free(self.bufferAllocation); self.completeList.deinit(main.globalAllocator);
self.coreFaces.deinit(main.globalAllocator);
self.optionalFaces.deinit(main.globalAllocator);
for(&self.neighborFacesSameLod) |*neighborFaces| {
neighborFaces.deinit(main.globalAllocator);
}
for(&self.neighborFacesHigherLod) |*neighborFaces| {
neighborFaces.deinit(main.globalAllocator);
}
main.globalAllocator.free(self.completeList);
} }
fn reset(self: *PrimitiveMesh) void { fn replaceRange(self: *PrimitiveMesh, group: FaceGroups, items: []const FaceData) void {
self.coreFaces.clearRetainingCapacity(); self.lock.lockWrite();
for(&self.neighborFacesSameLod) |*neighborFaces| { self.completeList.replaceRange(main.globalAllocator, group, items);
neighborFaces.clearRetainingCapacity(); self.lock.unlockWrite();
}
for(&self.neighborFacesHigherLod) |*neighborFaces| {
neighborFaces.clearRetainingCapacity();
}
self.optionalFaces.clearRetainingCapacity();
}
fn appendInternalQuadsToCore(self: *PrimitiveMesh, block: Block, x: i32, y: i32, z: i32, comptime backFace: bool, optional: bool) void {
const model = blocks.meshes.model(block);
models.models.items[model].appendInternalQuadsToList(if(optional) &self.optionalFaces else &self.coreFaces, main.globalAllocator, block, x, y, z, backFace);
}
fn appendNeighborFacingQuadsToCore(self: *PrimitiveMesh, block: Block, neighbor: chunk.Neighbor, x: i32, y: i32, z: i32, comptime backFace: bool, optional: bool) void {
const model = blocks.meshes.model(block);
models.models.items[model].appendNeighborFacingQuadsToList(if(optional) &self.optionalFaces else &self.coreFaces, main.globalAllocator, block, neighbor, x, y, z, backFace);
}
fn appendNeighborFacingQuadsToNeighbor(self: *PrimitiveMesh, block: Block, neighbor: chunk.Neighbor, x: i32, y: i32, z: i32, comptime backFace: bool, comptime isLod: bool) void {
const model = blocks.meshes.model(block);
if(isLod) {
models.models.items[model].appendNeighborFacingQuadsToList(&self.neighborFacesHigherLod[neighbor.reverse().toInt()], main.globalAllocator, block, neighbor, x, y, z, backFace);
} else {
models.models.items[model].appendNeighborFacingQuadsToList(&self.neighborFacesSameLod[neighbor.reverse().toInt()], main.globalAllocator, block, neighbor, x, y, z, backFace);
}
}
fn clearNeighbor(self: *PrimitiveMesh, neighbor: chunk.Neighbor, comptime isLod: bool) void {
if(isLod) {
self.neighborFacesHigherLod[neighbor.toInt()].clearRetainingCapacity();
} else {
self.neighborFacesSameLod[neighbor.toInt()].clearRetainingCapacity();
}
} }
fn finish(self: *PrimitiveMesh, parent: *ChunkMesh, lightList: *main.List(u32), lightMap: *std.AutoHashMap([4]u32, u16)) void { fn finish(self: *PrimitiveMesh, parent: *ChunkMesh, lightList: *main.List(u32), lightMap: *std.AutoHashMap([4]u32, u16)) void {
var len: usize = self.coreFaces.items.len;
for(self.neighborFacesSameLod) |neighborFaces| {
len += neighborFaces.items.len;
}
for(self.neighborFacesHigherLod) |neighborFaces| {
len += neighborFaces.items.len;
}
len += self.optionalFaces.items.len;
const completeList = main.globalAllocator.alloc(FaceData, len);
var i: usize = 0;
@memcpy(completeList[i..][0..self.coreFaces.items.len], self.coreFaces.items);
i += self.coreFaces.items.len;
for(self.neighborFacesSameLod) |neighborFaces| {
@memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items);
i += neighborFaces.items.len;
}
for(self.neighborFacesHigherLod) |neighborFaces| {
@memcpy(completeList[i..][0..neighborFaces.items.len], neighborFaces.items);
i += neighborFaces.items.len;
}
@memcpy(completeList[i..][0..self.optionalFaces.items.len], self.optionalFaces.items);
i += self.optionalFaces.items.len;
self.min = @splat(std.math.floatMax(f32)); self.min = @splat(std.math.floatMax(f32));
self.max = @splat(-std.math.floatMax(f32)); self.max = @splat(-std.math.floatMax(f32));
self.lock.lockRead();
parent.lightingData[0].lock.lockRead(); parent.lightingData[0].lock.lockRead();
parent.lightingData[1].lock.lockRead(); parent.lightingData[1].lock.lockRead();
for(completeList) |*face| { 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 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; const result = lightMap.getOrPut(light) catch unreachable;
if(!result.found_existing) { if(!result.found_existing) {
@ -403,20 +356,7 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh
} }
parent.lightingData[0].lock.unlockRead(); parent.lightingData[0].lock.unlockRead();
parent.lightingData[1].lock.unlockRead(); parent.lightingData[1].lock.unlockRead();
self.lock.unlockRead();
self.mutex.lock();
const oldList = self.completeList;
self.completeList = completeList;
self.coreLen = @intCast(self.coreFaces.items.len);
for(self.neighborFacesSameLod, 0..) |neighborFaces, j| {
self.sameLodLens[j] = @intCast(neighborFaces.items.len);
}
for(self.neighborFacesHigherLod, 0..) |neighborFaces, j| {
self.higherLodLens[j] = @intCast(neighborFaces.items.len);
}
self.optionalLen = @intCast(self.optionalFaces.items.len);
self.mutex.unlock();
main.globalAllocator.free(oldList);
} }
fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 { fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 {
@ -584,36 +524,28 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh
} }
fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) void { fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) void {
self.mutex.lock(); self.lock.lockRead();
defer self.mutex.unlock(); defer self.lock.unlockRead();
var len: u32 = self.coreLen; var len: usize = 0;
var offset: u32 = self.coreLen; const coreList = self.completeList.getRange(.core);
len += coreList.len;
const optionalList = self.completeList.getRange(.optional);
len += optionalList.len;
var list: [6][]FaceData = undefined; var list: [6][]FaceData = undefined;
for(0..6) |i| { for(0..6) |i| {
const neighborLen = self.sameLodLens[i];
if(!isNeighborLod[i]) { if(!isNeighborLod[i]) {
list[i] = self.completeList[offset..][0..neighborLen]; list[i] = self.completeList.getRange(.neighbor(@enumFromInt(i)));
len += neighborLen; } else {
list[i] = self.completeList.getRange(.neighborLod(@enumFromInt(i)));
} }
offset += neighborLen; len += list[i].len;
} }
for(0..6) |i| {
const neighborLen = self.higherLodLens[i];
if(isNeighborLod[i]) {
list[i] = self.completeList[offset..][0..neighborLen];
len += neighborLen;
}
offset += neighborLen;
}
const optionalList = self.completeList[offset..][0..self.optionalLen];
offset += self.optionalLen;
len += self.optionalLen;
const fullBuffer = faceBuffer.allocateAndMapRange(len, &self.bufferAllocation); const fullBuffer = faceBuffer.allocateAndMapRange(len, &self.bufferAllocation);
defer faceBuffer.unmapRange(fullBuffer); defer faceBuffer.unmapRange(fullBuffer);
// Sort the faces by normal to allow for backface culling on the GPU: // Sort the faces by normal to allow for backface culling on the GPU:
var i: u32 = 0; var i: u32 = 0;
var iStart = i; var iStart = i;
const coreList = self.completeList[0..self.coreLen];
for(0..7) |normal| { for(0..7) |normal| {
for(coreList) |face| { for(coreList) |face| {
if(main.models.extraQuadInfos.items[face.blockAndQuad.quadIndex].alignedNormalDirection) |normalDir| { if(main.models.extraQuadInfos.items[face.blockAndQuad.quadIndex].alignedNormalDirection) |normalDir| {
@ -870,10 +802,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
pub fn generateLightingData(self: *ChunkMesh) error{AlreadyStored}!void { pub fn generateLightingData(self: *ChunkMesh) error{AlreadyStored}!void {
self.mutex.lock();
self.opaqueMesh.reset();
self.transparentMesh.reset();
self.mutex.unlock();
try mesh_storage.addMeshToStorage(self); try mesh_storage.addMeshToStorage(self);
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator); var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
@ -922,6 +850,16 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
} }
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);
models.models.items[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);
models.models.items[model].appendNeighborFacingQuadsToList(list, allocator, block, neighbor, x, y, z, backFace);
}
pub fn generateMesh(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void { pub fn generateMesh(self: *ChunkMesh, lightRefreshList: *main.List(*ChunkMesh)) void {
var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined; var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&alwaysViewThroughMask), 0); @memset(std.mem.asBytes(&alwaysViewThroughMask), 0);
@ -933,6 +871,16 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
var hasFaces: [chunk.chunkSize][chunk.chunkSize]u32 = undefined; var hasFaces: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&hasFaces), 0); @memset(std.mem.asBytes(&hasFaces), 0);
self.mutex.lock(); 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 { const OcclusionInfo = packed struct {
canSeeNeighbor: u6 = 0, canSeeNeighbor: u6 = 0,
canSeeAllNeighbors: bool = false, canSeeAllNeighbors: bool = false,
@ -1029,9 +977,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(occlusionInfo.hasInternalQuads) { if(occlusionInfo.hasInternalQuads) {
const block = self.chunk.data.palette[paletteId]; const block = self.chunk.data.palette[paletteId];
if(block.transparent()) { if(block.transparent()) {
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false, false); appendInternalQuads(block, x, y, z, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendInternalQuadsToCore(block, x, y, z, false, false); appendInternalQuads(block, x, y, z, false, &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1055,11 +1003,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x - 1), @intCast(y), z, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false, initialAlwaysViewThroughMask[x - 1][y] & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x - 1), @intCast(y), z, false, if(initialAlwaysViewThroughMask[x - 1][y] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1082,11 +1030,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x + 1), @intCast(y), z, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false, initialAlwaysViewThroughMask[x + 1][y] & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x + 1), @intCast(y), z, false, if(initialAlwaysViewThroughMask[x + 1][y] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1109,11 +1057,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y - 1), z, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false, initialAlwaysViewThroughMask[x][y - 1] & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y - 1), z, false, if(initialAlwaysViewThroughMask[x][y - 1] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1136,11 +1084,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y + 1), z, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false, initialAlwaysViewThroughMask[x][y + 1] & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y + 1), z, false, if(initialAlwaysViewThroughMask[x][y + 1] & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1163,11 +1111,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z - 1, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false, initialAlwaysViewThroughMask[x][y] << 1 & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z - 1, false, if(initialAlwaysViewThroughMask[x][y] << 1 & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1190,11 +1138,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
} }
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), @intCast(x), @intCast(y), z, true, &transparentCore, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false, false); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z + 1, false, &transparentCore, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false, initialAlwaysViewThroughMask[x][y] >> 1 & setBit != 0); appendNeighborFacingQuads(block, neighbor, @intCast(x), @intCast(y), z + 1, false, if(initialAlwaysViewThroughMask[x][y] >> 1 & setBit != 0) &opaqueOptional else &opaqueCore, main.stackAllocator);
} }
} }
} }
@ -1203,6 +1151,12 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.mutex.unlock(); 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); self.finishNeighbors(lightRefreshList);
} }
@ -1243,10 +1197,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(neighborBlock.mode().dependsOnNeighbors) { if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) { if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
neighborChunkMesh.chunk.data.setValue(index, neighborBlock); neighborChunkMesh.chunk.data.setValue(index, neighborBlock);
neighborChunkMesh.opaqueMesh.coreFaces.clearRetainingCapacity();
neighborChunkMesh.transparentMesh.coreFaces.clearRetainingCapacity();
neighborChunkMesh.opaqueMesh.optionalFaces.clearRetainingCapacity();
neighborChunkMesh.transparentMesh.optionalFaces.clearRetainingCapacity();
neighborChunkMesh.mutex.unlock(); neighborChunkMesh.mutex.unlock();
neighborChunkMesh.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList); neighborChunkMesh.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
neighborChunkMesh.generateMesh(&lightRefreshList); neighborChunkMesh.generateMesh(&lightRefreshList);
@ -1302,10 +1252,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.lastNeighborsHigherLod[chunk.Neighbor.dirUp.toInt()] = null; self.lastNeighborsHigherLod[chunk.Neighbor.dirUp.toInt()] = null;
self.lastNeighborsSameLod[chunk.Neighbor.dirUp.toInt()] = null; self.lastNeighborsSameLod[chunk.Neighbor.dirUp.toInt()] = null;
} }
self.opaqueMesh.coreFaces.clearRetainingCapacity();
self.transparentMesh.coreFaces.clearRetainingCapacity();
self.opaqueMesh.optionalFaces.clearRetainingCapacity();
self.transparentMesh.optionalFaces.clearRetainingCapacity();
self.mutex.unlock(); self.mutex.unlock();
self.generateMesh(&lightRefreshList); // TODO: Batch mesh updates instead of applying them for each block changes. self.generateMesh(&lightRefreshList); // TODO: Batch mesh updates instead of applying them for each block changes.
self.mutex.lock(); self.mutex.lock();
@ -1319,7 +1265,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.uploadData(); self.uploadData();
} }
fn clearNeighbor(self: *ChunkMesh, neighbor: chunk.Neighbor, comptime isLod: bool) void { fn clearNeighborA(self: *ChunkMesh, neighbor: chunk.Neighbor, comptime isLod: bool) void {
self.opaqueMesh.clearNeighbor(neighbor, isLod); self.opaqueMesh.clearNeighbor(neighbor, isLod);
self.transparentMesh.clearNeighbor(neighbor, isLod); self.transparentMesh.clearNeighbor(neighbor, isLod);
} }
@ -1377,8 +1323,16 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(self.lastNeighborsSameLod[neighbor.toInt()] == neighborMesh) break :sameLodBlock; if(self.lastNeighborsSameLod[neighbor.toInt()] == neighborMesh) break :sameLodBlock;
self.lastNeighborsSameLod[neighbor.toInt()] = neighborMesh; self.lastNeighborsSameLod[neighbor.toInt()] = neighborMesh;
neighborMesh.lastNeighborsSameLod[neighbor.reverse().toInt()] = self; neighborMesh.lastNeighborsSameLod[neighbor.reverse().toInt()] = self;
self.clearNeighbor(neighbor, false);
neighborMesh.clearNeighbor(neighbor.reverse(), false); 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; const x3: i32 = if(neighbor.isPositive()) chunk.chunkMask else 0;
var x1: i32 = 0; var x1: i32 = 0;
while(x1 < chunk.chunkSize) : (x1 += 1) { while(x1 < chunk.chunkSize) : (x1 += 1) {
@ -1410,25 +1364,30 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
if(block.transparent()) { if(block.transparent()) {
if(block.hasBackFace()) { if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToNeighbor(block, neighbor.reverse(), x, y, z, true, false); appendNeighborFacingQuads(block, neighbor.reverse(), x, y, z, true, &transparentSelf, main.stackAllocator);
} }
neighborMesh.transparentMesh.appendNeighborFacingQuadsToNeighbor(block, neighbor, otherX, otherY, otherZ, false, false); appendNeighborFacingQuads(block, neighbor, otherX, otherY, otherZ, false, &transparentNeighbor, main.stackAllocator);
} else { } else {
neighborMesh.opaqueMesh.appendNeighborFacingQuadsToNeighbor(block, neighbor, otherX, otherY, otherZ, false, false); appendNeighborFacingQuads(block, neighbor, otherX, otherY, otherZ, false, &opaqueNeighbor, main.stackAllocator);
} }
} }
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) { if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) {
if(otherBlock.transparent()) { if(otherBlock.transparent()) {
if(otherBlock.hasBackFace()) { if(otherBlock.hasBackFace()) {
neighborMesh.transparentMesh.appendNeighborFacingQuadsToNeighbor(otherBlock, neighbor, otherX, otherY, otherZ, true, false); appendNeighborFacingQuads(otherBlock, neighbor, otherX, otherY, otherZ, true, &transparentNeighbor, main.stackAllocator);
} }
self.transparentMesh.appendNeighborFacingQuadsToNeighbor(otherBlock, neighbor.reverse(), x, y, z, false, false); appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &transparentSelf, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToNeighbor(otherBlock, neighbor.reverse(), x, y, z, false, false); 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.needsLightRefresh.store(true, .release);
neighborMesh.increaseRefCount(); neighborMesh.increaseRefCount();
lightRefreshList.append(neighborMesh); lightRefreshList.append(neighborMesh);
@ -1436,7 +1395,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.mutex.lock(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
if(self.lastNeighborsSameLod[neighbor.toInt()] != null) { if(self.lastNeighborsSameLod[neighbor.toInt()] != null) {
self.clearNeighbor(neighbor, false); self.opaqueMesh.replaceRange(.neighbor(neighbor), &.{});
self.transparentMesh.replaceRange(.neighbor(neighbor), &.{});
self.lastNeighborsSameLod[neighbor.toInt()] = null; self.lastNeighborsSameLod[neighbor.toInt()] = null;
} }
} }
@ -1446,7 +1406,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.mutex.lock(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) { if(self.lastNeighborsHigherLod[neighbor.toInt()] != null) {
self.clearNeighbor(neighbor, true); self.opaqueMesh.replaceRange(.neighborLod(neighbor), &.{});
self.transparentMesh.replaceRange(.neighborLod(neighbor), &.{});
self.lastNeighborsHigherLod[neighbor.toInt()] = null; self.lastNeighborsHigherLod[neighbor.toInt()] = null;
} }
continue; continue;
@ -1457,7 +1418,12 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
defer neighborMesh.mutex.unlock(); defer neighborMesh.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] == neighborMesh) continue; if(self.lastNeighborsHigherLod[neighbor.toInt()] == neighborMesh) continue;
self.lastNeighborsHigherLod[neighbor.toInt()] = neighborMesh; self.lastNeighborsHigherLod[neighbor.toInt()] = neighborMesh;
self.clearNeighbor(neighbor, true);
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 x3: i32 = if(neighbor.isPositive()) chunk.chunkMask else 0;
const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunk.chunkSize; const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunk.chunkSize;
const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunk.chunkSize; const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunk.chunkSize;
@ -1491,18 +1457,20 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant(); if(settings.leavesQuality == 0) otherBlock.typ = otherBlock.opaqueVariant();
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) { if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) {
if(otherBlock.transparent()) { if(otherBlock.transparent()) {
self.transparentMesh.appendNeighborFacingQuadsToNeighbor(otherBlock, neighbor.reverse(), x, y, z, false, true); appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &transparentSelf, main.stackAllocator);
} else { } else {
self.opaqueMesh.appendNeighborFacingQuadsToNeighbor(otherBlock, neighbor.reverse(), x, y, z, false, true); appendNeighborFacingQuads(otherBlock, neighbor.reverse(), x, y, z, false, &opaqueSelf, main.stackAllocator);
} }
} }
if(block.hasBackFace()) { if(block.hasBackFace()) {
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) { if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
self.transparentMesh.appendNeighborFacingQuadsToNeighbor(block, neighbor.reverse(), x, y, z, true, true); 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(); self.mutex.lock();
defer self.mutex.unlock(); defer self.mutex.unlock();
@ -1541,33 +1509,26 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
var needsUpdate: bool = false; var needsUpdate: bool = false;
if(self.transparentMesh.wasChanged) { if(self.transparentMesh.wasChanged) {
self.transparentMesh.wasChanged = false; self.transparentMesh.wasChanged = false;
self.transparentMesh.mutex.lock(); self.transparentMesh.lock.lockRead();
defer self.transparentMesh.mutex.unlock(); defer self.transparentMesh.lock.unlockRead();
var len: usize = self.transparentMesh.coreLen; var len: usize = 0;
var offset: usize = self.transparentMesh.coreLen; const coreList = self.transparentMesh.completeList.getRange(.core);
len += coreList.len;
var list: [6][]FaceData = undefined; var list: [6][]FaceData = undefined;
for(0..6) |i| { for(0..6) |i| {
const neighborLen = self.transparentMesh.sameLodLens[i];
if(!self.isNeighborLod[i]) { if(!self.isNeighborLod[i]) {
list[i] = self.transparentMesh.completeList[offset..][0..neighborLen]; list[i] = self.transparentMesh.completeList.getRange(.neighbor(@enumFromInt(i)));
len += neighborLen; } else {
list[i] = self.transparentMesh.completeList.getRange(.neighborLod(@enumFromInt(i)));
} }
offset += neighborLen; len += list[i].len;
}
for(0..6) |i| {
const neighborLen = self.transparentMesh.higherLodLens[i];
if(self.isNeighborLod[i]) {
list[i] = self.transparentMesh.completeList[offset..][0..neighborLen];
len += neighborLen;
}
offset += neighborLen;
} }
self.currentSorting = main.globalAllocator.realloc(self.currentSorting, len); self.currentSorting = main.globalAllocator.realloc(self.currentSorting, len);
self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, len + self.blockBreakingFaces.items.len); self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, len + self.blockBreakingFaces.items.len);
for(0..self.transparentMesh.coreLen) |i| { for(0..coreList.len) |i| {
self.currentSorting[i].face = self.transparentMesh.completeList[i]; self.currentSorting[i].face = coreList[i];
} }
offset = self.transparentMesh.coreLen; var offset = coreList.len;
for(0..6) |n| { for(0..6) |n| {
for(0..list[n].len) |i| { for(0..list[n].len) |i| {
self.currentSorting[offset + i].face = list[n][i]; self.currentSorting[offset + i].face = list[n][i];

View File

@ -980,7 +980,9 @@ fn addBreakingAnimationFace(pos: Vec3i, quadIndex: u16, texture: u16, neighbor:
defer mesh.mutex.unlock(); defer mesh.mutex.unlock();
const lightIndex = blk: { const lightIndex = blk: {
const meshData = if(isTransparent) &mesh.transparentMesh else &mesh.opaqueMesh; const meshData = if(isTransparent) &mesh.transparentMesh else &mesh.opaqueMesh;
for(meshData.completeList) |face| { meshData.lock.lockRead();
defer meshData.lock.unlockRead();
for(meshData.completeList.getEverything()) |face| {
if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) { if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) {
break :blk face.position.lightIndex; break :blk face.position.lightIndex;
} }

View File

@ -433,6 +433,85 @@ pub fn ListUnmanaged(comptime T: type) type {
}; };
} }
/// Holds multiple arrays sequentially in memory.
/// Allows addressing and remove each subarray individually, as well as iterating through all of them at once.
pub fn MultiArray(T: type, Range: type) type {
const size = @typeInfo(Range).@"enum".fields.len;
std.debug.assert(@typeInfo(Range).@"enum".is_exhaustive);
for(@typeInfo(Range).@"enum".fields) |field| {
std.debug.assert(field.value < size);
}
return struct {
offsets: [size + 1]usize = @splat(0),
items: [*]T = undefined,
capacity: usize = 0,
pub fn initCapacity(allocator: NeverFailingAllocator, capacity: usize) @This() {
return .{
.items = allocator.alloc(T, capacity)[0..0],
.capacity = capacity,
};
}
pub fn deinit(self: @This(), allocator: NeverFailingAllocator) void {
if(self.capacity != 0) {
allocator.free(self.items[0..self.capacity]);
}
}
pub fn clearAndFree(self: *@This(), allocator: NeverFailingAllocator) void {
self.deinit(allocator);
self.* = .{};
}
pub fn clearRetainingCapacity(self: *@This()) void {
self.offsets = @splat(0);
}
pub fn ensureCapacity(self: *@This(), allocator: NeverFailingAllocator, newCapacity: usize) void {
if(newCapacity <= self.capacity) return;
const newAllocation = allocator.realloc(self.items[0..self.capacity], newCapacity);
self.items = newAllocation.ptr;
self.capacity = newAllocation.len;
}
pub fn addMany(self: *@This(), allocator: NeverFailingAllocator, n: usize) []T {
self.ensureFreeCapacity(allocator, n);
return self.addManyAssumeCapacity(n);
}
pub fn replaceRange(self: *@This(), allocator: NeverFailingAllocator, range: Range, elems: []const T) void {
const i: usize = @intFromEnum(range);
const oldLen = self.offsets[i + 1] - self.offsets[i];
self.ensureCapacity(allocator, self.offsets[size] - oldLen + elems.len);
const startIndex = self.offsets[i + 1];
const newStartIndex = self.offsets[i + 1] - oldLen + elems.len;
const endIndex = self.offsets[size];
const newEndIndex = self.offsets[size] - oldLen + elems.len;
if(newStartIndex > startIndex) {
std.mem.copyBackwards(T, self.items[newStartIndex..newEndIndex], self.items[startIndex..endIndex]);
} else {
std.mem.copyForwards(T, self.items[newStartIndex..newEndIndex], self.items[startIndex..endIndex]);
}
@memcpy(self.items[self.offsets[i]..][0..elems.len], elems);
for(self.offsets[i + 1 ..]) |*offset| {
offset.* = offset.* - oldLen + elems.len;
}
}
pub fn getRange(self: *@This(), range: Range) []T {
const i: usize = @intFromEnum(range);
const startIndex = self.offsets[i];
const endIndex = self.offsets[i + 1];
return self.items[startIndex..endIndex];
}
pub fn getEverything(self: *@This()) []T {
return self.items[0..self.offsets[size]];
}
};
}
const page_size_min = std.heap.page_size_min; const page_size_min = std.heap.page_size_min;
const page_size_max = std.heap.page_size_max; const page_size_max = std.heap.page_size_max;
const pageSize = std.heap.pageSize; const pageSize = std.heap.pageSize;