Preallocate texture VRAM, move files around

This commit is contained in:
2026-05-22 23:38:16 +02:00
parent fd311b97e7
commit c971443b02
12 changed files with 237 additions and 85 deletions

263
src/Chunk.zig Normal file
View File

@@ -0,0 +1,263 @@
const Chunk = @This();
const std = @import("std");
const ctx = @import("AppContext.zig");
const math = @import("math.zig");
const shaders = @import("shaders.zig");
const vk = @import("vulkan");
const vm = @import("vecmath");
const voxels = @import("voxels.zig");
const Blocks = @import("assets/Blocks.zig");
const CommandBuffer = @import("engine/CommandBuffer.zig");
const Engine = @import("engine/Engine.zig");
const Game = @import("Game.zig");
const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer;
const Materials = @import("engine/Materials.zig");
const ObjectUniformsBuffer = GenericBuffer(void, shaders.ObjectUniforms);
pub const chunk_size = 16;
const initial_capacity = 256;
var next_chunk_id: std.atomic.Value(u64) = .init(0);
/// To get the block at coordinates (x, y, z) relative to the chunk, read the
/// value at `blocks[z][y][x]`.
blocks: [chunk_size][chunk_size][chunk_size]Blocks.Id,
origin: vm.Vector3,
descriptor_set: vk.DescriptorSet,
object_buffer: ObjectUniformsBuffer,
object_count: u32,
/// For debug purposes, a unique "name" per chunk instance.
chunk_id: u64,
pub const InitInfo = struct {
origin: vm.Vector3,
descriptor_pool: vk.DescriptorPool,
per_batch_descriptor_set_layout: vk.DescriptorSetLayout,
};
pub const Neighbors = std.enums.EnumFieldStruct(voxels.Orientation, ?*const Chunk, null);
pub fn init(init_info: InitInfo) !Chunk {
const engine = ctx.engine;
const descriptor_set = try engine.allocateDescriptorSet(.{
.descriptor_pool = init_info.descriptor_pool,
.set_layout = init_info.per_batch_descriptor_set_layout,
});
errdefer engine.freeDescriptorSet(init_info.descriptor_pool, descriptor_set);
var object_buffer: ObjectUniformsBuffer = try .init(.{
.usage = .storage,
.target_queue = .graphics,
.array_capacity = initial_capacity,
});
errdefer object_buffer.deinit();
try engine.updateDescriptorSets(.{
.writes = &.{
.{
.dst_set = descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = object_buffer.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
},
});
const chunk_id = next_chunk_id.fetchAdd(1, .monotonic);
engine.setObjectName(descriptor_set, "DS Chunk[{d}]", .{chunk_id});
engine.setObjectName(object_buffer.buffer, "B Chunk[{d}]", .{chunk_id});
engine.setObjectName(object_buffer.device_memory, "DM Chunk[{d}]", .{chunk_id});
return .{
.blocks = .{.{[_]Blocks.Id{.air} ** chunk_size} ** chunk_size} ** chunk_size,
.origin = init_info.origin,
.descriptor_set = descriptor_set,
.object_buffer = object_buffer,
.object_count = 0,
.chunk_id = chunk_id,
};
}
pub fn deinit(self: *Chunk, descriptor_pool: vk.DescriptorPool) void {
const engine = ctx.engine;
std.log.scoped(.deinit).debug("Deinitializing chunk {d} with {s}#{X}", .{ self.chunk_id, @typeName(vk.DescriptorPool), @intFromEnum(descriptor_pool) });
self.object_buffer.deinit();
engine.freeDescriptorSet(descriptor_pool, self.descriptor_set);
self.* = undefined;
}
pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void {
const allocator_frame = ctx.allocator_frame;
const engine = ctx.engine;
var uniforms: std.ArrayList(shaders.ObjectUniforms) = try .initCapacity(allocator_frame, initial_capacity);
for (self.blocks, 0..) |plane, iz| {
const fz: f32 = @floatFromInt(iz);
for (plane, 0..) |row, iy| {
const fy: f32 = @floatFromInt(iy);
for (row, 0..) |id, ix| {
const fx: f32 = @floatFromInt(ix);
const block: *const Blocks.Definition = &blocks.array.items[id.toInt()];
const origin = vm.Vector3.add(self.origin, .init(fx, fy, fz));
inline for (@typeInfo(voxels.Orientation).@"enum".fields) |field| {
const side = @field(voxels.Orientation, field.name);
const material = @field(block.walls, field.name).material;
if (material != .empty and self.getOpposite(side, ix, iy, iz, blocks, neighbors) == .empty) {
const matrix = getMatrix(side, origin);
try uniforms.append(allocator_frame, .{
.matrixOStoWS = matrix,
.matrixOStoWSNormal = matrix,
.material = material,
});
}
}
}
}
}
const object_count: u32 = @intCast(uniforms.items.len);
if (self.object_buffer.array_capacity < object_count) {
const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count);
const new_object_buffer: ObjectUniformsBuffer = try .init(.{
.usage = .storage,
.target_queue = .graphics,
.array_capacity = desired_capacity,
});
try engine.updateDescriptorSets(.{
.writes = &.{
.{
.dst_set = self.descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = new_object_buffer.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
},
});
self.object_buffer.deinit();
self.object_buffer = new_object_buffer;
engine.setObjectName(new_object_buffer.buffer, "B Chunk[{d}]", .{self.chunk_id});
engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id});
}
try self.object_buffer.write(.{ .elements = uniforms.items });
self.object_count = object_count;
}
pub fn draw(self: *const Chunk, layout: vk.PipelineLayout, command_buffer: CommandBuffer) void {
command_buffer.bindDescriptorSets(.graphics, layout, 1, &.{self.descriptor_set}, &.{});
command_buffer.drawIndexed(.{ .index_count = 6, .instance_count = self.object_count });
}
fn getOpposite(self: *const Chunk, comptime side: voxels.Orientation, x: usize, y: usize, z: usize, blocks: *const Blocks, neighbors: Neighbors) Materials.Id {
return switch (side) {
.positive_x => if (x + 1 < chunk_size)
blocks.array.items[self.blocks[z][y][x + 1].toInt()].walls.negative_x.material
else if (neighbors.positive_x) |neighbor|
blocks.array.items[neighbor.blocks[z][y][0].toInt()].walls.negative_x.material
else
.empty,
.negative_x => if (x > 0)
blocks.array.items[self.blocks[z][y][x - 1].toInt()].walls.positive_x.material
else if (neighbors.negative_x) |neighbor|
blocks.array.items[neighbor.blocks[z][y][chunk_size - 1].toInt()].walls.positive_x.material
else
.empty,
.positive_y => if (y + 1 < chunk_size)
blocks.array.items[self.blocks[z][y + 1][x].toInt()].walls.negative_y.material
else if (neighbors.positive_y) |neighbor|
blocks.array.items[neighbor.blocks[z][0][x].toInt()].walls.negative_y.material
else
.empty,
.negative_y => if (y > 0)
blocks.array.items[self.blocks[z][y - 1][x].toInt()].walls.positive_y.material
else if (neighbors.negative_y) |neighbor|
blocks.array.items[neighbor.blocks[z][chunk_size - 1][x].toInt()].walls.positive_y.material
else
.empty,
.positive_z => if (z + 1 < chunk_size)
blocks.array.items[self.blocks[z + 1][y][x].toInt()].walls.negative_z.material
else if (neighbors.positive_z) |neighbor|
blocks.array.items[neighbor.blocks[0][y][x].toInt()].walls.negative_z.material
else
.empty,
.negative_z => if (z > 0)
blocks.array.items[self.blocks[z - 1][y][x].toInt()].walls.positive_z.material
else if (neighbors.negative_z) |neighbor|
blocks.array.items[neighbor.blocks[chunk_size - 1][y][x].toInt()].walls.positive_z.material
else
.empty,
};
}
fn getMatrix(comptime side: voxels.Orientation, origin: vm.Vector3) vm.Matrix4x4 {
return switch (side) {
// zig fmt: off
.positive_x => .init(
0, 1, 0, 0,
0, 0, 1, 0,
1, 0, 0, 0,
origin.x + 1, origin.y, origin.z, 1,
),
.negative_x => .init(
0, -1, 0, 0,
0, 0, 1, 0,
-1, 0, 0, 0,
origin.x, origin.y + 1, origin.z, 1,
),
.positive_y => .init(
-1, 0, 0, 0,
0, 0, 1, 0,
0, 1, 0, 0,
origin.x + 1, origin.y + 1, origin.z, 1,
),
.negative_y => .init(
1, 0, 0, 0,
0, 0, 1, 0,
0, -1, 0, 0,
origin.x, origin.y, origin.z, 1,
),
.positive_z => .init(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
origin.x, origin.y, origin.z + 1, 1,
),
.negative_z => .init(
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, -1, 0,
origin.x, origin.y + 1, origin.z, 1,
),
// zig fmt: on
};
}