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 ListUnmanaged = @import("utils/list.zig").ListUnmanaged;
pub const MultiArray = @import("utils/list.zig").MultiArray;
pub const VirtualList = @import("utils/list.zig").VirtualList;
const file_monitor = utils.file_monitor;

View File

@ -286,16 +286,32 @@ pub const IndirectData = extern struct {
};
const PrimitiveMesh = struct { // MARK: PrimitiveMesh
coreFaces: main.ListUnmanaged(FaceData) = .{},
neighborFacesSameLod: [6]main.ListUnmanaged(FaceData) = @splat(.{}),
neighborFacesHigherLod: [6]main.ListUnmanaged(FaceData) = @splat(.{}),
optionalFaces: main.ListUnmanaged(FaceData) = .{},
completeList: []FaceData = &.{},
coreLen: u32 = 0,
sameLodLens: [6]u32 = @splat(0),
higherLodLens: [6]u32 = @splat(0),
optionalLen: u32 = 0,
mutex: std.Thread.Mutex = .{},
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),
@ -304,86 +320,23 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh
max: Vec3f = undefined,
fn deinit(self: *PrimitiveMesh) void {
faceBuffer.free(self.bufferAllocation);
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);
self.completeList.deinit(main.globalAllocator);
}
fn reset(self: *PrimitiveMesh) void {
self.coreFaces.clearRetainingCapacity();
for(&self.neighborFacesSameLod) |*neighborFaces| {
neighborFaces.clearRetainingCapacity();
}
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 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 {
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.max = @splat(-std.math.floatMax(f32));
self.lock.lockRead();
parent.lightingData[0].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 result = lightMap.getOrPut(light) catch unreachable;
if(!result.found_existing) {
@ -403,20 +356,7 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh
}
parent.lightingData[0].lock.unlockRead();
parent.lightingData[1].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);
self.lock.unlockRead();
}
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 {
self.mutex.lock();
defer self.mutex.unlock();
var len: u32 = self.coreLen;
var offset: u32 = self.coreLen;
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| {
const neighborLen = self.sameLodLens[i];
if(!isNeighborLod[i]) {
list[i] = self.completeList[offset..][0..neighborLen];
len += neighborLen;
list[i] = self.completeList.getRange(.neighbor(@enumFromInt(i)));
} 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);
defer faceBuffer.unmapRange(fullBuffer);
// Sort the faces by normal to allow for backface culling on the GPU:
var i: u32 = 0;
var iStart = i;
const coreList = self.completeList[0..self.coreLen];
for(0..7) |normal| {
for(coreList) |face| {
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 {
self.mutex.lock();
self.opaqueMesh.reset();
self.transparentMesh.reset();
self.mutex.unlock();
try mesh_storage.addMeshToStorage(self);
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 {
var alwaysViewThroughMask: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&alwaysViewThroughMask), 0);
@ -933,6 +871,16 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
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,
@ -1029,9 +977,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(occlusionInfo.hasInternalQuads) {
const block = self.chunk.data.palette[paletteId];
if(block.transparent()) {
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false, false);
appendInternalQuads(block, x, y, z, false, &transparentCore, main.stackAllocator);
} 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.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 {
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.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 {
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.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 {
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.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 {
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.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 {
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.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 {
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.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);
}
@ -1243,10 +1197,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(neighborBlock.mode().dependsOnNeighbors) {
if(neighborBlock.mode().updateData(&neighborBlock, neighbor.reverse(), newBlock)) {
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.updateBlockLight(@intCast(nx & chunk.chunkMask), @intCast(ny & chunk.chunkMask), @intCast(nz & chunk.chunkMask), neighborBlock, &lightRefreshList);
neighborChunkMesh.generateMesh(&lightRefreshList);
@ -1302,10 +1252,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.lastNeighborsHigherLod[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.generateMesh(&lightRefreshList); // TODO: Batch mesh updates instead of applying them for each block changes.
self.mutex.lock();
@ -1319,7 +1265,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
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.transparentMesh.clearNeighbor(neighbor, isLod);
}
@ -1377,8 +1323,16 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(self.lastNeighborsSameLod[neighbor.toInt()] == neighborMesh) break :sameLodBlock;
self.lastNeighborsSameLod[neighbor.toInt()] = neighborMesh;
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;
var x1: i32 = 0;
while(x1 < chunk.chunkSize) : (x1 += 1) {
@ -1410,25 +1364,30 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
if(block.transparent()) {
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 {
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(otherBlock.transparent()) {
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 {
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.increaseRefCount();
lightRefreshList.append(neighborMesh);
@ -1436,7 +1395,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.mutex.lock();
defer self.mutex.unlock();
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;
}
}
@ -1446,7 +1406,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
self.mutex.lock();
defer self.mutex.unlock();
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;
}
continue;
@ -1457,7 +1418,12 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
defer neighborMesh.mutex.unlock();
if(self.lastNeighborsHigherLod[neighbor.toInt()] == neighborMesh) continue;
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 offsetX = @divExact(self.pos.wx, 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(canBeSeenThroughOtherBlock(otherBlock, block, neighbor.reverse())) {
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 {
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(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();
defer self.mutex.unlock();
@ -1541,33 +1509,26 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
var needsUpdate: bool = false;
if(self.transparentMesh.wasChanged) {
self.transparentMesh.wasChanged = false;
self.transparentMesh.mutex.lock();
defer self.transparentMesh.mutex.unlock();
var len: usize = self.transparentMesh.coreLen;
var offset: usize = self.transparentMesh.coreLen;
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| {
const neighborLen = self.transparentMesh.sameLodLens[i];
if(!self.isNeighborLod[i]) {
list[i] = self.transparentMesh.completeList[offset..][0..neighborLen];
len += neighborLen;
list[i] = self.transparentMesh.completeList.getRange(.neighbor(@enumFromInt(i)));
} else {
list[i] = self.transparentMesh.completeList.getRange(.neighborLod(@enumFromInt(i)));
}
offset += neighborLen;
}
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;
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..self.transparentMesh.coreLen) |i| {
self.currentSorting[i].face = self.transparentMesh.completeList[i];
for(0..coreList.len) |i| {
self.currentSorting[i].face = coreList[i];
}
offset = self.transparentMesh.coreLen;
var offset = coreList.len;
for(0..6) |n| {
for(0..list[n].len) |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();
const lightIndex = blk: {
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) {
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_max = std.heap.page_size_max;
const pageSize = std.heap.pageSize;