diff --git a/assets/blocks/Andesite.json b/assets/blocks/Andesite.json new file mode 100644 index 0000000..4af44cb --- /dev/null +++ b/assets/blocks/Andesite.json @@ -0,0 +1,3 @@ +{ + "material": "Andesite.json" +} diff --git a/assets/blocks/Bricks.json b/assets/blocks/Bricks.json new file mode 100644 index 0000000..fdb6f09 --- /dev/null +++ b/assets/blocks/Bricks.json @@ -0,0 +1,3 @@ +{ + "material": "Bricks.json" +} diff --git a/assets/blocks/ChiseledStoneBricks.json b/assets/blocks/ChiseledStoneBricks.json new file mode 100644 index 0000000..68f22b4 --- /dev/null +++ b/assets/blocks/ChiseledStoneBricks.json @@ -0,0 +1,3 @@ +{ + "material": "ChiseledStoneBricks.json" +} diff --git a/assets/blocks/CoalBlock.json b/assets/blocks/CoalBlock.json new file mode 100644 index 0000000..61ac645 --- /dev/null +++ b/assets/blocks/CoalBlock.json @@ -0,0 +1,3 @@ +{ + "material": "CoalBlock.json" +} diff --git a/assets/blocks/CoalOre.json b/assets/blocks/CoalOre.json new file mode 100644 index 0000000..d9a99f9 --- /dev/null +++ b/assets/blocks/CoalOre.json @@ -0,0 +1,3 @@ +{ + "material": "CoalOre.json" +} diff --git a/assets/blocks/Cobblestone.json b/assets/blocks/Cobblestone.json new file mode 100644 index 0000000..f427fdb --- /dev/null +++ b/assets/blocks/Cobblestone.json @@ -0,0 +1,3 @@ +{ + "material": "Cobblestone.json" +} diff --git a/assets/blocks/DiamondBlock.json b/assets/blocks/DiamondBlock.json new file mode 100644 index 0000000..bc49801 --- /dev/null +++ b/assets/blocks/DiamondBlock.json @@ -0,0 +1,3 @@ +{ + "material": "DiamondBlock.json" +} diff --git a/assets/blocks/DiamondOre.json b/assets/blocks/DiamondOre.json new file mode 100644 index 0000000..dd236da --- /dev/null +++ b/assets/blocks/DiamondOre.json @@ -0,0 +1,3 @@ +{ + "material": "DiamondOre.json" +} diff --git a/assets/blocks/Diorite.json b/assets/blocks/Diorite.json new file mode 100644 index 0000000..b1208c4 --- /dev/null +++ b/assets/blocks/Diorite.json @@ -0,0 +1,3 @@ +{ + "material": "Diorite.json" +} diff --git a/assets/blocks/Dirt.json b/assets/blocks/Dirt.json new file mode 100644 index 0000000..a1b40ac --- /dev/null +++ b/assets/blocks/Dirt.json @@ -0,0 +1,3 @@ +{ + "material": "Dirt.json" +} diff --git a/assets/blocks/Glass.json b/assets/blocks/Glass.json new file mode 100644 index 0000000..d121c8f --- /dev/null +++ b/assets/blocks/Glass.json @@ -0,0 +1,3 @@ +{ + "material": "Glass.json" +} diff --git a/assets/blocks/GoldBlock.json b/assets/blocks/GoldBlock.json new file mode 100644 index 0000000..2384de0 --- /dev/null +++ b/assets/blocks/GoldBlock.json @@ -0,0 +1,3 @@ +{ + "material": "GoldBlock.json" +} diff --git a/assets/blocks/GoldOre.json b/assets/blocks/GoldOre.json new file mode 100644 index 0000000..fc342a1 --- /dev/null +++ b/assets/blocks/GoldOre.json @@ -0,0 +1,3 @@ +{ + "material": "GoldOre.json" +} diff --git a/assets/blocks/Granite.json b/assets/blocks/Granite.json new file mode 100644 index 0000000..55fb938 --- /dev/null +++ b/assets/blocks/Granite.json @@ -0,0 +1,3 @@ +{ + "material": "Granite.json" +} diff --git a/assets/blocks/Gravel.json b/assets/blocks/Gravel.json new file mode 100644 index 0000000..a3543e8 --- /dev/null +++ b/assets/blocks/Gravel.json @@ -0,0 +1,3 @@ +{ + "material": "Gravel.json" +} diff --git a/assets/blocks/IronBlock.json b/assets/blocks/IronBlock.json new file mode 100644 index 0000000..c35bad9 --- /dev/null +++ b/assets/blocks/IronBlock.json @@ -0,0 +1,3 @@ +{ + "material": "IronBlock.json" +} diff --git a/assets/blocks/IronOre.json b/assets/blocks/IronOre.json new file mode 100644 index 0000000..a0e5c21 --- /dev/null +++ b/assets/blocks/IronOre.json @@ -0,0 +1,3 @@ +{ + "material": "IronOre.json" +} diff --git a/assets/blocks/OakLogX.json b/assets/blocks/OakLogX.json new file mode 100644 index 0000000..91fac1e --- /dev/null +++ b/assets/blocks/OakLogX.json @@ -0,0 +1,10 @@ +{ + "materials": [ + "OakLogTop.json", + "OakLogTop.json", + "OakLog.json", + "OakLog.json", + "OakLog.json", + "OakLog.json" + ] +} diff --git a/assets/blocks/OakLogY.json b/assets/blocks/OakLogY.json new file mode 100644 index 0000000..015bcc7 --- /dev/null +++ b/assets/blocks/OakLogY.json @@ -0,0 +1,10 @@ +{ + "materials": [ + "OakLog.json", + "OakLog.json", + "OakLogTop.json", + "OakLogTop.json", + "OakLog.json", + "OakLog.json" + ] +} diff --git a/assets/blocks/OakLogZ.json b/assets/blocks/OakLogZ.json new file mode 100644 index 0000000..d86b3bf --- /dev/null +++ b/assets/blocks/OakLogZ.json @@ -0,0 +1,10 @@ +{ + "materials": [ + "OakLog.json", + "OakLog.json", + "OakLog.json", + "OakLog.json", + "OakLogTop.json", + "OakLogTop.json" + ] +} diff --git a/assets/blocks/OakPlanks.json b/assets/blocks/OakPlanks.json new file mode 100644 index 0000000..9d546e9 --- /dev/null +++ b/assets/blocks/OakPlanks.json @@ -0,0 +1,3 @@ +{ + "material": "OakPlanks.json" +} diff --git a/assets/blocks/Sand.json b/assets/blocks/Sand.json new file mode 100644 index 0000000..6bc203d --- /dev/null +++ b/assets/blocks/Sand.json @@ -0,0 +1,3 @@ +{ + "material": "Sand.json" +} diff --git a/assets/blocks/SmoothStone.json b/assets/blocks/SmoothStone.json new file mode 100644 index 0000000..de18a32 --- /dev/null +++ b/assets/blocks/SmoothStone.json @@ -0,0 +1,3 @@ +{ + "material": "SmoothStone.json" +} diff --git a/assets/blocks/Stone.json b/assets/blocks/Stone.json new file mode 100644 index 0000000..b66847b --- /dev/null +++ b/assets/blocks/Stone.json @@ -0,0 +1,3 @@ +{ + "material": "Stone.json" +} diff --git a/assets/blocks/StoneBricks.json b/assets/blocks/StoneBricks.json new file mode 100644 index 0000000..a54f8e0 --- /dev/null +++ b/assets/blocks/StoneBricks.json @@ -0,0 +1,3 @@ +{ + "material": "StoneBricks.json" +} diff --git a/src/Game.zig b/src/Game.zig index da4a217..8ac5e05 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -6,6 +6,8 @@ const vk = @import("vulkan"); const math = @import("math.zig"); +const Blocks = @import("assets/Blocks.zig"); +const Chunk = @import("assets/Chunk.zig"); const Materials = @import("assets/Materials.zig"); const Textures = @import("assets/Textures.zig"); const CommandBuffer = @import("engine/CommandBuffer.zig").CommandBuffer; @@ -58,7 +60,7 @@ const GlobalUniforms = extern struct { } }; -const ObjectUniforms = extern struct { +pub const ObjectUniforms = extern struct { matrixOStoWS: [16]f32, matrixOStoWSNormal: [16]f32, @@ -106,8 +108,7 @@ swapchain: *Swapchain, global_descriptor_set_layout: vk.DescriptorSetLayout, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, descriptor_pool: vk.DescriptorPool, -/// [0] GLOBAL, [1] PER BATCH -descriptor_sets: [2]vk.DescriptorSet, +global_descriptor_set: vk.DescriptorSet, pipeline_layout: vk.PipelineLayout, pipeline: vk.Pipeline, @@ -120,12 +121,12 @@ global_uniforms_transfer_command_buffer: CommandBuffer(.graphics, .persistent), global_uniforms_transfer_semaphores: []vk.Semaphore, point_lights: PointLightBuffer, directional_lights: DirectionalLightBuffer, -object_uniforms: ObjectUniformsBuffer, sampler: vk.Sampler, -object_count: u32, +blocks: Blocks, materials: Materials, textures: Textures, +chunks: std.ArrayList(Chunk), camera_position: Vector3 = .init(0, 0, 1.62), camera_pitch: f32 = 0, @@ -144,7 +145,7 @@ const max_directional_lights = 4; const max_objects = 1024; const camera_near_plane = 0.1; -const camera_vertical_fov_deg = 90.0; +const camera_vertical_fov_deg = 80.0; const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg; const camera_mouse_sensitivity = 0.002; @@ -158,7 +159,10 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain var textures = try Textures.init(engine, allocator); errdefer textures.deinit(engine, allocator); - materials.loadAll(engine, &textures, allocator); + var blocks = try Blocks.init(allocator); + errdefer blocks.deinit(allocator); + + blocks.loadAll(engine, &materials, &textures, allocator); const sampler = try engine.createSampler(.{ .mag_filter = .linear, @@ -359,13 +363,6 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }); errdefer directional_lights.deinit(engine); - var object_uniforms = try ObjectUniformsBuffer.init(engine, .{ - .usage = .storage, - .target_queue = .graphics, - .array_capacity = max_objects, - }); - errdefer object_uniforms.deinit(engine); - const pipeline = try engine.createGraphicsPipeline(.{ .stages = &.{ .{ @@ -484,7 +481,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain errdefer engine.destroyPipeline(pipeline); const descriptor_pool = try engine.createDescriptorPool(.{ - .max_sets = 2, + .max_sets = 1 + 256, .pool_sizes = &.{ .{ .type = .sampler, @@ -500,19 +497,17 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, .{ .type = .storage_buffer, - .descriptor_count = 4, + .descriptor_count = 3 + 256, }, }, }); errdefer engine.destroyDescriptorPool(descriptor_pool); - // [0] GLOBAL, [1] PER BATCH - var descriptor_sets: [2]vk.DescriptorSet = undefined; - try engine.allocateDescriptorSets(.{ + const global_descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = descriptor_pool, - .set_layouts = &.{ global_descriptor_set_layout, per_batch_descriptor_set_layout }, - .variable_descriptor_counts = &.{ @intCast(textures.textures.items.len), 0 }, - }, &descriptor_sets); + .set_layout = global_descriptor_set_layout, + .variable_descriptor_count = @intCast(textures.textures.items.len), + }); const descriptor_images = try allocator.alloc(vk.DescriptorImageInfo, textures.textures.items.len); for (textures.textures.items, descriptor_images) |texture, *info| { @@ -527,7 +522,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain try engine.updateDescriptorSets(.{ .writes = &.{ .{ - .dst_set = descriptor_sets[0], + .dst_set = global_descriptor_set, .dst_binding = 0, .dst_array_element = 0, .descriptor_type = .uniform_buffer, @@ -542,7 +537,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, }, .{ - .dst_set = descriptor_sets[0], + .dst_set = global_descriptor_set, .dst_binding = 1, .dst_array_element = 0, .descriptor_type = .storage_buffer, @@ -557,7 +552,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, }, .{ - .dst_set = descriptor_sets[0], + .dst_set = global_descriptor_set, .dst_binding = 2, .dst_array_element = 0, .descriptor_type = .storage_buffer, @@ -572,7 +567,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, }, .{ - .dst_set = descriptor_sets[0], + .dst_set = global_descriptor_set, .dst_binding = 3, .dst_array_element = 0, .descriptor_type = .storage_buffer, @@ -587,52 +582,45 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, }, .{ - .dst_set = descriptor_sets[0], + .dst_set = global_descriptor_set, .dst_binding = 5, .dst_array_element = 0, .descriptor_type = .sampled_image, .descriptor_infos = .{ .image = descriptor_images }, }, - .{ - .dst_set = descriptor_sets[1], - .dst_binding = 0, - .dst_array_element = 0, - .descriptor_type = .storage_buffer, - .descriptor_infos = .{ - .buffer = &.{ - .{ - .buffer = object_uniforms.buffer, - .offset = 0, - .range = vk.WHOLE_SIZE, - }, - }, - }, - }, }, }); - const point_lights_data: []const PointLight = &.{ - .{ - .positionWS = .{ 0, 0, 0.5 }, - .color = .{ 1, 1, 1 }, - }, - .{ - .positionWS = .{ -5, 5, 0.5 }, - .color = .{ 1, 0, 0 }, - }, - .{ - .positionWS = .{ 5, 5, 0.5 }, - .color = .{ 0, 0, 1 }, - }, - .{ - .positionWS = .{ -5, -5, 0.5 }, - .color = .{ 0, 1, 0 }, - }, - .{ - .positionWS = .{ 5, -5, 0.5 }, - .color = .{ 1, 1, 0 }, - }, - }; + var chunks: std.ArrayList(Chunk) = .empty; + errdefer { + for (chunks.items) |*chunk| { + chunk.deinit(engine, descriptor_pool); + } + chunks.deinit(allocator); + } + + var it: Iterator3 = .init(.init(-64, -64, 0), .init(64, 64, 0), .init(16, 16, 16)); + while (it.next()) |origin| { + try chunks.ensureUnusedCapacity(allocator, 1); + chunks.appendAssumeCapacity(try Chunk.init(engine, .{ + .origin = origin, + .descriptor_pool = descriptor_pool, + .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, + })); + + const chunk = &chunks.items[chunks.items.len - 1]; + + var it2 = Iterator3.init(.init(0, 0, 0), .init(1, 1, 0), .{ .vector = @splat(1.0 / 16.0) }); + while (it2.next()) |fpos| { + const x, const y, const z = fpos.asArrayNorm(u4); + const block: Blocks.Id = @enumFromInt(engine.random.intRangeLessThan(u12, 1, @intCast(blocks.blocks.items.len))); + chunk.blocks[z][y][x] = block; + } + + try chunk.refresh(engine, &blocks, allocator); + } + + const point_lights_data: []const PointLight = &.{}; try point_lights.write(engine, .{ .header = @intCast(point_lights_data.len), .elements = point_lights_data, @@ -649,26 +637,6 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .elements = directional_lights_data, }); - const object_count: u32 = blk: { - var objects: std.ArrayList(ObjectUniforms) = try .initCapacity(allocator, 578); - defer objects.deinit(allocator); - - var it = Iterator3.init(.init(-8, -8, 0), .init(8, 8, 2), .init(1, 1, 2)); - while (it.next()) |pos| { - const material: Materials.Id = @enumFromInt(engine.random.uintLessThan(u16, @intFromEnum(materials.next_id))); - const matrix_os_to_ws = Matrix4x4.initTranslation(pos); - const matrix_os_to_ws_normal = Matrix4x4.inverseTransposeAffine(matrix_os_to_ws); - objects.appendAssumeCapacity(.{ - .matrixOStoWS = matrix_os_to_ws.asArray(), - .matrixOStoWSNormal = matrix_os_to_ws_normal.asArray(), - .material = material, - }); - } - try object_uniforms.write(engine, .{ .elements = objects.items }); - - break :blk @intCast(objects.items.len); - }; - return .{ .allocator = allocator, .engine = engine, @@ -676,7 +644,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .global_descriptor_set_layout = global_descriptor_set_layout, .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, .descriptor_pool = descriptor_pool, - .descriptor_sets = descriptor_sets, + .global_descriptor_set = global_descriptor_set, .pipeline_layout = pipeline_layout, .pipeline = pipeline, @@ -689,12 +657,12 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores, .point_lights = point_lights, .directional_lights = directional_lights, - .object_uniforms = object_uniforms, .sampler = sampler, - .object_count = object_count, + .blocks = blocks, .materials = materials, .textures = textures, + .chunks = chunks, }; } @@ -703,13 +671,16 @@ pub fn deinit(self: *Game) void { self.vertex_buffer.deinit(self.engine); self.index_buffer.deinit(self.engine); + for (self.chunks.items) |*chunk| { + chunk.deinit(self.engine, self.descriptor_pool); + } + self.chunks.deinit(self.allocator); self.global_uniforms.deinit(self.engine); self.global_uniforms_staging_buffer.deinit(self.engine); self.global_uniforms_transfer_command_buffer.deinit(self.engine); self.point_lights.deinit(self.engine); self.directional_lights.deinit(self.engine); - self.object_uniforms.deinit(self.engine); for (self.global_uniforms_transfer_semaphores) |semaphore| { self.engine.destroySemaphore(semaphore); @@ -725,6 +696,7 @@ pub fn deinit(self: *Game) void { self.textures.deinit(self.engine, self.allocator); self.materials.deinit(self.engine, self.allocator); + self.blocks.deinit(self.allocator); self.* = undefined; } @@ -768,7 +740,7 @@ pub fn update(self: *Game, dt: f32) void { ); // zig fmt: on - const ambient_light = Vector3.init(0.2, 0.2, 0.2); + const ambient_light = Vector3.init(0.001, 0.001, 0.001); const global_uniforms_data: GlobalUniforms = .{ .matrixWStoVS = matrix_ws_to_vs.asArray(), @@ -926,12 +898,11 @@ fn render(self: *Game) !void { }, }); command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); - command_buffer.bindDescriptorSets(.graphics, self.pipeline_layout, 0, &self.descriptor_sets, &.{}); + command_buffer.bindDescriptorSet(.graphics, self.pipeline_layout, 0, self.global_descriptor_set, null); - command_buffer.drawIndexed(.{ - .index_count = self.index_buffer.array_capacity, - .instance_count = self.object_count, - }); + for (self.chunks.items) |chunk| { + chunk.draw(self.pipeline_layout, command_buffer); + } } try command_buffer.endCommandBuffer(); diff --git a/src/assets/Blocks.zig b/src/assets/Blocks.zig new file mode 100644 index 0000000..fc8baa2 --- /dev/null +++ b/src/assets/Blocks.zig @@ -0,0 +1,262 @@ +const Blocks = @This(); +const std = @import("std"); + +const atoms = @import("../engine/atoms.zig"); +const Engine = @import("../engine/Engine.zig"); +const Materials = @import("Materials.zig"); +const Textures = @import("Textures.zig"); + +pub const Block = struct { + positive_x: Materials.Id, + negative_x: Materials.Id, + positive_y: Materials.Id, + negative_y: Materials.Id, + positive_z: Materials.Id, + negative_z: Materials.Id, + + pub fn initUniform(material: Materials.Id) Block { + return .{ + .positive_x = material, + .negative_x = material, + .positive_y = material, + .negative_y = material, + .positive_z = material, + .negative_z = material, + }; + } + + pub fn initSeparate( + positive_x: Materials.Id, + negative_x: Materials.Id, + positive_y: Materials.Id, + negative_y: Materials.Id, + positive_z: Materials.Id, + negative_z: Materials.Id, + ) Block { + return .{ + .positive_x = positive_x, + .negative_x = negative_x, + .positive_y = positive_y, + .negative_y = negative_y, + .positive_z = positive_z, + .negative_z = negative_z, + }; + } + + pub fn initArray(materials: [6]Materials.Id) Block { + return .{ + .positive_x = materials[0], + .negative_x = materials[1], + .positive_y = materials[2], + .negative_y = materials[3], + .positive_z = materials[4], + .negative_z = materials[5], + }; + } +}; + +map: Map, +blocks: Array, + +pub const capacity = std.math.maxInt(std.meta.Tag(Id)); + +pub const Key = struct { atom: atoms.Atom }; +pub const Id = enum(u12) { + air = 0, + _, + + pub fn next(self: Id) Id { + return @enumFromInt(@intFromEnum(self) + 1); + } +}; + +pub const Map = std.AutoHashMapUnmanaged(Key, Id); +pub const Array = std.ArrayList(Block); + +pub fn init(allocator: std.mem.Allocator) !Blocks { + var map: Map = .empty; + errdefer map.deinit(allocator); + try map.ensureTotalCapacity(allocator, capacity); + + var blocks: Array = try .initCapacity(allocator, capacity); + errdefer blocks.deinit(allocator); + + const air = Block.initUniform(.empty); + blocks.appendAssumeCapacity(air); + + return .{ + .map = map, + .blocks = blocks, + }; +} + +pub fn deinit(self: *Blocks, allocator: std.mem.Allocator) void { + std.log.debug("Deinitializing {*} with Allocator{{{*},{*}}}", .{ self, allocator.ptr, allocator.vtable }); + + self.blocks.deinit(allocator); + self.map.deinit(allocator); + self.* = undefined; +} + +pub fn getAtom(self: *const Blocks, atom: atoms.Atom) ?Id { + const key: Key = .{ .atom = atom }; + return self.map.get(key); +} + +pub fn getFilename(self: *const Blocks, filename: []const u8) ?Id { + const atom = atoms.getAtom(filename) orelse return null; + const key: Key = .{ .atom = atom }; + return self.map.get(key); +} + +pub fn getBlock(self: *const Blocks, id: Id) ?*Block { + const index: usize = @intFromEnum(id); + return if (index < self.blocks.items.len) &self.blocks.items[index] else null; +} + +pub fn getOrLoadAtom( + self: *Blocks, + engine: *Engine, + materials: *Materials, + textures: *Textures, + atom: atoms.Atom, + temp_allocator: std.mem.Allocator, +) !Id { + const key: Key = .{ .atom = atom }; + const entry = self.map.getOrPutAssumeCapacity(key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer _ = self.map.remove(key); + const block = try loadBlock(engine, materials, textures, atoms.getString(atom), temp_allocator); + const id = self.nextId(); + entry.value_ptr.* = id; + self.blocks.appendAssumeCapacity(block); + return id; + } +} + +pub fn getOrLoadFilename( + self: *Blocks, + engine: *Engine, + materials: *Materials, + textures: *Textures, + filename: []const u8, + temp_allocator: std.mem.Allocator, +) !Id { + const atom = try atoms.getOrPutAtom(filename); + const key: Key = .{ .atom = atom }; + const entry = self.map.getOrPutAssumeCapacity(key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer _ = self.map.remove(key); + const texture = try loadBlock(engine, materials, textures, filename, temp_allocator); + const id = self.nextId(); + entry.value_ptr.* = id; + self.blocks.appendAssumeCapacity(texture); + return id; + } +} + +pub fn loadAll( + self: *Blocks, + engine: *Engine, + materials: *Materials, + textures: *Textures, + temp_allocator: std.mem.Allocator, +) void { + const cwd = std.fs.cwd(); + + var dir = cwd.openDir("assets/blocks", .{ .iterate = true }) catch |err| { + std.log.err("Error while opening blocks directory: {s}", .{@errorName(err)}); + return; + }; + defer dir.close(); + + var it = dir.iterate(); + while (it.next() catch |err| { + std.log.err("Error while iterating over blocks directory: {s}", .{@errorName(err)}); + return; + }) |entry| { + if (entry.kind != .file) { + std.log.warn("Skipping block entry {s}, which is not a file", .{entry.name}); + continue; + } + + _ = self.getOrLoadFilename(engine, materials, textures, entry.name, temp_allocator) catch |err| { + std.log.err("Error while loading block entry {s}: {s}", .{ entry.name, @errorName(err) }); + }; + } +} + +fn loadBlock( + engine: *Engine, + materials: *Materials, + textures: *Textures, + filename: []const u8, + temp_allocator: std.mem.Allocator, +) !Block { + const BlockJson = struct { + material: ?[]const u8 = null, + materials: ?[6][]const u8 = null, + }; + + std.log.debug("Loading block \"{s}\"...", .{filename}); + + const cwd = std.fs.cwd(); + + var dir = try cwd.openDir("assets/blocks", .{}); + defer dir.close(); + + // NOTE Buffer size approximated based on expected JSON structure. + var buffer: [256]u8 = undefined; + + const file = try dir.openFile(filename, .{}); + defer file.close(); + + var file_reader = file.reader(&buffer); + var json_reader: std.json.Reader = .init(temp_allocator, &file_reader.interface); + defer json_reader.deinit(); + + const parsed: std.json.Parsed(BlockJson) = try std.json.parseFromTokenSource(BlockJson, temp_allocator, &json_reader, .{ + .duplicate_field_behavior = .@"error", + .ignore_unknown_fields = false, + .allocate = .alloc_if_needed, + }); + defer parsed.deinit(); + + const block_json = parsed.value; + + const block: Block = blk: { + if (block_json.material) |name| { + if (block_json.materials != null) { + std.log.err("Block entry {s} has both properties \"material\" and \"materials\" defined, but exactly one of them must be defined", .{filename}); + return error.ParseError; + } + + const material = try materials.getOrLoadFilename(engine, textures, name, temp_allocator); + break :blk .initUniform(material); + } + + if (block_json.materials) |names| { + var ids: [6]Materials.Id = undefined; + for (names, &ids) |name, *id| { + id.* = try materials.getOrLoadFilename(engine, textures, name, temp_allocator); + } + break :blk .initArray(ids); + } + + std.log.err("Block entry {s} has neither \"material\" or \"materials\" properties defined, but exactly one of them must be defined", .{filename}); + return error.ParseError; + }; + + return block; +} + +fn nextId(self: *const Blocks) Id { + const index = self.blocks.items.len; + return @enumFromInt(index); +} diff --git a/src/assets/Chunk.zig b/src/assets/Chunk.zig new file mode 100644 index 0000000..9b391c4 --- /dev/null +++ b/src/assets/Chunk.zig @@ -0,0 +1,238 @@ +const Chunk = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const math = @import("../math.zig"); + +const Blocks = @import("Blocks.zig"); +const CommandBuffer = @import("../engine/CommandBuffer.zig").CommandBuffer; +const Engine = @import("../engine/Engine.zig"); +const Game = @import("../Game.zig"); +const GenericBuffer = @import("../engine/GenericBuffer.zig").GenericBuffer; + +const Matrix4x4 = math.Matrix4x4; +const ObjectUniformsBuffer = GenericBuffer(void, Game.ObjectUniforms); +const Vector3 = math.Vector3; + +pub const chunk_size = 16; + +const initial_capacity = 256; + +blocks: [chunk_size][chunk_size][chunk_size]Blocks.Id, +origin: Vector3, +descriptor_set: vk.DescriptorSet, +object_buffer: ObjectUniformsBuffer, +object_count: u32, + +pub const InitInfo = struct { + origin: Vector3, + descriptor_pool: vk.DescriptorPool, + per_batch_descriptor_set_layout: vk.DescriptorSetLayout, +}; + +pub fn init(engine: *Engine, init_info: InitInfo) !Chunk { + 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(engine, .{ + .usage = .storage, + .target_queue = .graphics, + .array_capacity = initial_capacity, + }); + errdefer object_buffer.deinit(engine); + + 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, + }, + }, + }, + }, + }, + }); + + 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, + }; +} + +pub fn deinit(self: *Chunk, engine: *Engine, descriptor_pool: vk.DescriptorPool) void { + self.object_buffer.deinit(engine); + engine.freeDescriptorSet(descriptor_pool, self.descriptor_set); + + self.* = undefined; +} + +pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, temp_allocator: std.mem.Allocator) !void { + var object_count: u32 = 0; + + for (self.blocks) |plane| { + for (plane) |row| { + for (row) |id| { + const block = blocks.getBlock(id).?; + object_count += @intFromBool(block.positive_x != .empty); + object_count += @intFromBool(block.negative_x != .empty); + object_count += @intFromBool(block.positive_y != .empty); + object_count += @intFromBool(block.negative_y != .empty); + object_count += @intFromBool(block.positive_z != .empty); + object_count += @intFromBool(block.negative_z != .empty); + } + } + } + + if (self.object_buffer.array_capacity < object_count) { + const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count); + const new_object_buffer: ObjectUniformsBuffer = try .init(engine, .{ + .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(engine); + self.object_buffer = new_object_buffer; + } + + const uniforms = try temp_allocator.alloc(Game.ObjectUniforms, object_count); + var object_i: usize = 0; + defer temp_allocator.free(uniforms); + + 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 = blocks.getBlock(id).?; + + const center = Vector3.add(self.origin, .init(fx, fy, fz)); + const cx, const cy, const cz = center.asArray(); + + if (block.positive_x != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 0, + cx + 0.5, cy, cz, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.positive_x); + object_i += 1; + } + + if (block.negative_x != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 0, -1, 0, 0, + 0, 0, 1, 0, + -1, 0, 0, 0, + cx - 0.5, cy, cz, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.negative_x); + object_i += 1; + } + + if (block.positive_y != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + -1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + cx, cy + 0.5, cz, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.positive_y); + object_i += 1; + } + + if (block.negative_y != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + cx, cy - 0.5, cz, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.negative_y); + object_i += 1; + } + + if (block.positive_z != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + cx, cy, cz + 0.5, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.positive_z); + object_i += 1; + } + + if (block.negative_z != .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + cx, cy, cz - 0.5, 1, + ); + // zig fmt: on + uniforms[object_i] = .init(matrix, matrix, block.negative_z); + object_i += 1; + } + } + } + } + + std.debug.assert(object_i == uniforms.len); + + try self.object_buffer.write(engine, .{ .elements = uniforms }); + self.object_count = object_count; +} + +pub fn draw(self: *const Chunk, layout: vk.PipelineLayout, command_buffer: CommandBuffer(.graphics, .transient)) void { + command_buffer.bindDescriptorSets(.graphics, layout, 1, &.{self.descriptor_set}, &.{}); + command_buffer.drawIndexed(.{ .index_count = 6, .instance_count = self.object_count }); +} diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig index 5a80263..57b6df0 100644 --- a/src/assets/Materials.zig +++ b/src/assets/Materials.zig @@ -19,6 +19,7 @@ pub const capacity = std.math.maxInt(std.meta.Tag(Id)); pub const Key = struct { atom: atoms.Atom }; pub const Id = enum(u16) { + empty, _, pub fn next(self: Id) Id { @@ -55,10 +56,12 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Materials { }); errdefer material_buffer.deinit(engine); + // TODO Add "error" material to represent the empty value (it shouldn't be rendered, so it's good to know when it is). + return .{ .map = map, .material_buffer = material_buffer, - .next_id = @enumFromInt(0), + .next_id = @enumFromInt(1), }; } diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig index a0ae592..5c0d7e4 100644 --- a/src/assets/Textures.zig +++ b/src/assets/Textures.zig @@ -37,12 +37,12 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { errdefer map.deinit(allocator); try map.ensureTotalCapacity(allocator, capacity); - var array: Array = try .initCapacity(allocator, capacity); + var textures: Array = try .initCapacity(allocator, capacity); errdefer { - for (array.items) |*texture| { + for (textures.items) |*texture| { texture.deinit(engine); } - array.deinit(allocator); + textures.deinit(allocator); } const empty_base_color_texture = try Texture.init(engine, .{ @@ -51,6 +51,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { .usage = .base_color, .target_queue = .graphics, }); + textures.appendAssumeCapacity(empty_base_color_texture); const empty_emissive_texture = try Texture.init(engine, .{ .width = 1, @@ -58,6 +59,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { .usage = .emissive, .target_queue = .graphics, }); + textures.appendAssumeCapacity(empty_emissive_texture); const empty_normal_texture = try Texture.init(engine, .{ .width = 1, @@ -65,6 +67,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { .usage = .normal, .target_queue = .graphics, }); + textures.appendAssumeCapacity(empty_normal_texture); const empty_occlusuion_roughness_metallic_texture = try Texture.init(engine, .{ .width = 1, @@ -72,13 +75,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { .usage = .occlusion_roughness_metallic, .target_queue = .graphics, }); - - array.appendSliceAssumeCapacity(&.{ - empty_base_color_texture, - empty_emissive_texture, - empty_normal_texture, - empty_occlusuion_roughness_metallic_texture, - }); + textures.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); try empty_base_color_texture.writeSamples(u8, engine, &.{ 255, 255, 255, 255 }); try empty_emissive_texture.writeSamples(f16, engine, &.{ 1.0, 1.0, 1.0, 1.0 }); @@ -87,7 +84,7 @@ pub fn init(engine: *Engine, allocator: std.mem.Allocator) !Textures { return .{ .map = map, - .textures = array, + .textures = textures, }; } @@ -114,14 +111,10 @@ pub fn getFilename(self: *const Textures, filename: []const u8, usage: Texture.U } pub fn getTexture(self: *const Textures, id: Id) ?*Texture { - const index: usize = id.id; + const index: usize = @intFromEnum(id); return if (index < self.textures.items.len) &self.textures.items[index] else null; } -pub fn getId(self: *const Textures, key: Key) ?Id { - return self.map.get(key); -} - pub fn getOrLoadAtom(self: *Textures, engine: *Engine, atom: atoms.Atom, usage: Texture.Usage, temp_allocator: std.mem.Allocator) !Id { const key: Key = .{ .atom = atom, .usage = usage }; const entry = self.map.getOrPutAssumeCapacity(key); @@ -130,7 +123,7 @@ pub fn getOrLoadAtom(self: *Textures, engine: *Engine, atom: atoms.Atom, usage: return entry.value_ptr.*; } else { errdefer _ = self.map.remove(key); - const texture = try self.loadTexture(engine, atoms.getString(atom), usage, temp_allocator); + const texture = try loadTexture(engine, atoms.getString(atom), usage, temp_allocator); const id = self.nextId(); entry.value_ptr.* = id; self.textures.appendAssumeCapacity(texture); diff --git a/src/engine/CommandBuffer.zig b/src/engine/CommandBuffer.zig index 3f78284..2aa7e42 100644 --- a/src/engine/CommandBuffer.zig +++ b/src/engine/CommandBuffer.zig @@ -92,6 +92,23 @@ pub fn CommandBuffer(comptime queue_type: QueueType, comptime transient: Transie }, contents); } + pub inline fn bindDescriptorSet( + self: Self, + pipeline_bind_point: vk.PipelineBindPoint, + layout: vk.PipelineLayout, + set: u32, + descriptor_set: vk.DescriptorSet, + dynamic_offset: ?u32, + ) void { + self.bindDescriptorSets( + pipeline_bind_point, + layout, + set, + &.{descriptor_set}, + if (dynamic_offset) |x| &.{x} else &.{}, + ); + } + pub inline fn bindDescriptorSets( self: Self, pipeline_bind_point: vk.PipelineBindPoint, diff --git a/src/engine/Engine.zig b/src/engine/Engine.zig index 205439e..1c28869 100644 --- a/src/engine/Engine.zig +++ b/src/engine/Engine.zig @@ -570,6 +570,12 @@ pub const DescriptorPoolCreateInfo = struct { }; pub const DescriptorSetAllocateInfo = struct { + descriptor_pool: vk.DescriptorPool, + set_layout: vk.DescriptorSetLayout, + variable_descriptor_count: ?u32 = null, +}; + +pub const DescriptorSetsAllocateInfo = struct { descriptor_pool: vk.DescriptorPool, set_layouts: []const vk.DescriptorSetLayout, variable_descriptor_counts: []const u32 = &.{}, @@ -733,7 +739,17 @@ pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer { return buffer; } -pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void { +pub fn allocateDescriptorSet(self: *Engine, allocate_info: DescriptorSetAllocateInfo) !vk.DescriptorSet { + var descriptor_sets: [1]vk.DescriptorSet = undefined; + try self.allocateDescriptorSets(.{ + .descriptor_pool = allocate_info.descriptor_pool, + .set_layouts = &.{allocate_info.set_layout}, + .variable_descriptor_counts = if (allocate_info.variable_descriptor_count) |x| &.{x} else &.{}, + }, &descriptor_sets); + return descriptor_sets[0]; +} + +pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetsAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void { std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len); const has_variable_descriptor_counts = allocate_info.variable_descriptor_counts.len > 0; @@ -1031,6 +1047,11 @@ pub fn destroyShaderModule(self: *Engine, shader_module: vk.ShaderModule) void { self.device.destroyShaderModule(shader_module, &self.vk_allocator.interface); } +pub fn freeDescriptorSet(self: *Engine, descriptor_pool: vk.DescriptorPool, descriptor_set: vk.DescriptorSet) void { + const descriptor_sets = [_]vk.DescriptorSet{descriptor_set}; + self.device.freeDescriptorSets(descriptor_pool, descriptor_sets.len, &descriptor_sets) catch unreachable; +} + pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void { self.device.freeMemory(device_memory, &self.vk_allocator.interface); }