Files
voxel-game/src/Game.zig

1131 lines
39 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const Game = @This();
const std = @import("std");
const c = @import("const.zig");
const ctx = @import("AppContext.zig");
const glfw = @import("zglfw");
const math = @import("math.zig");
const media = @import("media");
const shaders = @import("shaders.zig");
const vk = @import("vulkan");
const vm = @import("vecmath");
const worldgen = @import("worldgen.zig");
const Blocks = @import("assets/Blocks.zig");
const Chunk = @import("Chunk.zig");
const Chunks = @import("Chunks.zig");
const CommandBuffer = @import("engine/CommandBuffer.zig");
const Engine = @import("engine/Engine.zig");
const Fonts = @import("engine/Fonts.zig");
const Iterator2 = math.Iterator2;
const Materials = @import("engine/Materials.zig");
const Player = @import("Player.zig");
const Skybox = @import("engine/Skybox.zig");
const Swapchain = @import("engine/Swapchain.zig");
const Texture = @import("engine/Texture.zig");
const Textures = @import("engine/Textures.zig");
global_descriptor_set_layout: vk.DescriptorSetLayout,
per_batch_descriptor_set_layout: vk.DescriptorSetLayout,
descriptor_pool: vk.DescriptorPool,
global_descriptor_set: vk.DescriptorSet,
pipeline_layout: vk.PipelineLayout,
pipeline: vk.Pipeline,
vertex_buffer: shaders.VertexBuffer,
index_buffer: shaders.IndexBuffer,
global_uniforms: shaders.GlobalUniformsBuffer,
global_uniforms_staging_buffer: shaders.GlobalUniformsBuffer,
global_uniforms_staging_ptr: *shaders.GlobalUniforms,
global_uniforms_transfer_semaphores: []vk.Semaphore,
point_lights: shaders.PointLightBuffer,
directional_lights: shaders.DirectionalLightBuffer,
sampler: vk.Sampler,
deferred_command_buffers: std.ArrayList(CommandBuffer),
blocks: Blocks,
chunks: Chunks,
skybox: Skybox,
player: Player,
font: Fonts.Id,
const max_point_lights = 1024;
const max_directional_lights = 4;
const chunk_descriptor_pool = 1024;
const camera_near_plane = 0.1;
const point_lights_data = [_]shaders.PointLight{};
const directional_lights_data = [_]shaders.DirectionalLight{
.{
.directionWS = .unit_nz,
.color = .init(0.3, 0.3, 0.3),
},
};
pub fn init() !Game {
const allocator_general = ctx.allocator_general;
const allocator_frame = ctx.allocator_frame;
const engine = ctx.engine;
const swapchain = ctx.swapchain;
const textures = ctx.textures;
const materials = ctx.materials;
const fonts = ctx.fonts;
const gui = ctx.gui;
var blocks = try Blocks.init();
errdefer blocks.deinit();
// JANK HACK When this line is removed, capturing a frame with RenderDoc
// will crash the game with segfault reading address 0x140 (presumably
// within librenderdoc.so).
blocks.loadAll();
const sampler = try engine.createSampler(.{
.mag_filter = .linear,
.min_filter = .linear,
.mipmap_mode = .linear,
.address_mode_u = .clamp_to_edge,
.address_mode_v = .clamp_to_edge,
.address_mode_w = .clamp_to_edge,
.mip_lod_bias = 0,
.anisotropy_enable = .false,
.max_anisotropy = 0,
.compare_enable = .false,
.compare_op = .always,
.min_lod = 0,
.max_lod = vk.LOD_CLAMP_NONE,
.border_color = .float_transparent_black,
.unnormalized_coordinates = .false,
});
errdefer engine.destroySampler(sampler);
const global_descriptor_set_layout = try engine.createDescriptorSetLayout(.{
.bindings = &.{
.{
.binding = 0,
.descriptor_type = .uniform_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
.{
.binding = 1,
.descriptor_type = .storage_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
.{
.binding = 2,
.descriptor_type = .storage_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
.{
.binding = 3,
.descriptor_type = .storage_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
.{
.binding = 4,
.descriptor_type = .sampler,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
.immutable_samplers = &.{sampler},
},
.{
.binding = 5,
.descriptor_type = .sampled_image,
.descriptor_count = Textures.max_textures,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
},
});
errdefer engine.destroyDescriptorSetLayout(global_descriptor_set_layout);
engine.setObjectName(global_descriptor_set_layout, "DSL Global", .{});
const per_batch_descriptor_set_layout = try engine.createDescriptorSetLayout(.{
.bindings = &.{
.{
.binding = 0,
.descriptor_type = .storage_buffer,
.descriptor_count = 1,
.stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
},
},
});
errdefer engine.destroyDescriptorSetLayout(per_batch_descriptor_set_layout);
engine.setObjectName(per_batch_descriptor_set_layout, "DSL PerBatch", .{});
const pipeline_layout = try engine.createPipelineLayout(.{
.set_layouts = &.{
global_descriptor_set_layout,
per_batch_descriptor_set_layout,
},
});
errdefer engine.destroyPipelineLayout(pipeline_layout);
engine.setObjectName(pipeline_layout, "PL Main", .{});
const shader = try engine.createShaderModule(.{ .code = &shaders.main_spv });
defer engine.destroyShaderModule(shader);
engine.setObjectName(shader, "SM main", .{});
var vertex_buffer = try shaders.VertexBuffer.init(.{
.usage = .vertex,
.target_queue = .graphics,
.capacity = 4,
.name = "QuadVB",
});
errdefer vertex_buffer.deinit();
try vertex_buffer.writeSlice(&.{
.init(
.init(0, 0, 0),
.init(0, 1),
.init(0, 0, 1),
.init(1, 0, 0, -1),
),
.init(
.init(1, 0, 0),
.init(1, 1),
.init(0, 0, 1),
.init(1, 0, 0, -1),
),
.init(
.init(0, 1, 0),
.init(0, 0),
.init(0, 0, 1),
.init(1, 0, 0, -1),
),
.init(
.init(1, 1, 0),
.init(1, 0),
.init(0, 0, 1),
.init(1, 0, 0, -1),
),
});
var index_buffer = try shaders.IndexBuffer.init(.{
.usage = .index,
.target_queue = .graphics,
.capacity = 6,
.name = "QuadIB",
});
errdefer index_buffer.deinit();
try index_buffer.writeSlice(&.{ 0, 1, 2, 2, 1, 3 });
var global_uniforms = try shaders.GlobalUniformsBuffer.init(.{
.usage = .uniform,
.target_queue = .graphics,
.name = "GlobalUniforms",
});
errdefer global_uniforms.deinit();
var global_uniforms_staging_buffer = try shaders.GlobalUniformsBuffer.init(.{
.usage = .staging,
.target_queue = .graphics,
.mappable = true,
.name = "GlobalUniforms Staging",
});
errdefer global_uniforms_staging_buffer.deinit();
const global_uniforms_staging_ptr = try global_uniforms_staging_buffer.map();
errdefer global_uniforms_staging_buffer.unmap();
const global_uniforms_transfer_semaphores = blk: {
var semaphores: std.ArrayList(vk.Semaphore) = try .initCapacity(allocator_general, swapchain.swapchain_images.len);
errdefer semaphores.deinit(allocator_general);
errdefer for (semaphores.items) |x| engine.destroySemaphore(x);
for (0..swapchain.swapchain_images.len) |i| {
const semaphore = try engine.createSemaphore();
engine.setObjectName(semaphore, "S Transfer[{d}]", .{i});
semaphores.appendAssumeCapacity(semaphore);
}
break :blk try semaphores.toOwnedSlice(allocator_general);
};
errdefer {
for (global_uniforms_transfer_semaphores) |semaphore| {
engine.destroySemaphore(semaphore);
}
allocator_general.free(global_uniforms_transfer_semaphores);
}
var point_lights = try shaders.PointLightBuffer.init(.{
.usage = .storage,
.target_queue = .graphics,
.capacity = max_point_lights,
.name = "PointLights",
});
errdefer point_lights.deinit();
var directional_lights = try shaders.DirectionalLightBuffer.init(.{
.usage = .storage,
.target_queue = .graphics,
.capacity = max_directional_lights,
.name = "DirectionalLights",
});
errdefer directional_lights.deinit();
const pipeline = try engine.createGraphicsPipeline(.{
.stages = &.{
.{
.stage = .{ .vertex_bit = true },
.module = shader,
.name = "vertexMain",
},
.{
.stage = .{ .fragment_bit = true },
.module = shader,
.name = "fragmentMain",
},
},
.vertex_input_state = .{
.vertex_binding_descriptions = &.{
.{
.binding = 0,
.stride = @sizeOf(shaders.Vertex),
.input_rate = .vertex,
},
},
.vertex_attribute_descriptions = &.{
.{
.location = 0,
.binding = 0,
.format = .r32g32b32_sfloat,
.offset = @offsetOf(shaders.Vertex, "positionOS"),
},
.{
.location = 1,
.binding = 0,
.format = .r16g16_unorm,
.offset = @offsetOf(shaders.Vertex, "texCoord"),
},
.{
.location = 2,
.binding = 0,
.format = .r8g8b8_snorm,
.offset = @offsetOf(shaders.Vertex, "normalOS"),
},
.{
.location = 3,
.binding = 0,
.format = .r8g8b8a8_snorm,
.offset = @offsetOf(shaders.Vertex, "tangentOS"),
},
},
},
.input_assembly_state = .{
.topology = .triangle_list,
.primitive_restart_enable = .false,
},
.viewport_state = .{
.viewports = &.{undefined}, // dynamic
.scissors = &.{undefined}, // dynamic
},
.rasterization_state = .{
.depth_clamp_enable = .false,
.rasterizer_discard_enable = .false,
.polygon_mode = .fill,
.cull_mode = .{ .back_bit = true },
.front_face = .counter_clockwise,
.depth_bias_enable = .false,
.depth_bias_constant_factor = 0,
.depth_bias_clamp = 0,
.depth_bias_slope_factor = 0,
.line_width = 1,
},
.multisample_state = .{
.rasterization_samples = .{ .@"1_bit" = true },
.sample_shading_enable = .false,
.min_sample_shading = 1,
.alpha_to_coverage_enable = .false,
.alpha_to_one_enable = .false,
},
.depth_stencil_state = .{
.depth_test_enable = .true,
.depth_write_enable = .true,
.depth_compare_op = .greater,
.depth_bounds_test_enable = .false,
.stencil_test_enable = .false,
.front = .{
.fail_op = .keep,
.pass_op = .keep,
.depth_fail_op = .keep,
.compare_op = .never,
.compare_mask = 0,
.write_mask = 0,
.reference = 0,
},
.back = .{
.fail_op = .keep,
.pass_op = .keep,
.depth_fail_op = .keep,
.compare_op = .never,
.compare_mask = 0,
.write_mask = 0,
.reference = 0,
},
.min_depth_bounds = 0,
.max_depth_bounds = 1,
},
.color_blend_state = .{
.logic_op_enable = .false,
.logic_op = .copy,
.attachments = &.{
.{
.blend_enable = .false,
.src_color_blend_factor = .one,
.dst_color_blend_factor = .zero,
.color_blend_op = .add,
.src_alpha_blend_factor = .one,
.dst_alpha_blend_factor = .zero,
.alpha_blend_op = .add,
.color_write_mask = .{
.r_bit = true,
.g_bit = true,
.b_bit = true,
.a_bit = true,
},
},
},
.blend_constants = .{ 0, 0, 0, 0 },
},
.dynamic_state = .{
.dynamic_states = &.{ .viewport, .scissor },
},
.layout = pipeline_layout,
.view_mask = 0,
.color_attachment_formats = &.{swapchain.params.surface_format.format},
.depth_attachment_format = Texture.Usage.depth.vkFormat(),
.stencil_attachment_format = .undefined,
});
errdefer engine.destroyPipeline(pipeline);
engine.setObjectName(pipeline, "P Main", .{});
const descriptor_pool = try engine.createDescriptorPool(.{
.flags = .{
.free_descriptor_set_bit = true,
},
.max_sets = 1 + chunk_descriptor_pool,
.pool_sizes = &.{
.{
.type = .sampler,
.descriptor_count = 1,
},
.{
.type = .sampled_image,
.descriptor_count = Textures.max_textures,
},
.{
.type = .uniform_buffer,
.descriptor_count = 1,
},
.{
.type = .storage_buffer,
.descriptor_count = 3 + chunk_descriptor_pool,
},
},
});
errdefer engine.destroyDescriptorPool(descriptor_pool);
engine.setObjectName(descriptor_pool, "DP Main", .{});
const global_descriptor_set = try engine.allocateDescriptorSet(.{
.descriptor_pool = descriptor_pool,
.set_layout = global_descriptor_set_layout,
});
engine.setObjectName(global_descriptor_set, "DS Global", .{});
const block_grass = try blocks.getOrLoad("Grass.json");
const block_dirt = try blocks.getOrLoad("Dirt.json");
const block_stone = try blocks.getOrLoad("Stone.json");
const block_bedrock = try blocks.getOrLoad("Bedrock.json");
const font_id = try fonts.load("FiraMono-Regular.ttf", .{
.codepoint_ranges = &.{
.{ .start = 32, .end = 127 },
},
.name = "Fira Mono",
});
// VOLATILE Load all assets before this point
const descriptor_images = try allocator_frame.alloc(vk.DescriptorImageInfo, textures.array.items.len);
for (textures.array.items, descriptor_images) |texture, *info| {
info.* = .{
.sampler = .null_handle,
.image_view = texture.image_view,
.image_layout = .shader_read_only_optimal,
};
}
try engine.updateDescriptorSets(.{
.writes = &.{
.{
.dst_set = global_descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .uniform_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = global_uniforms.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = global_descriptor_set,
.dst_binding = 1,
.dst_array_element = 0,
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = point_lights.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = global_descriptor_set,
.dst_binding = 2,
.dst_array_element = 0,
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = directional_lights.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = global_descriptor_set,
.dst_binding = 3,
.dst_array_element = 0,
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = materials.material_buffer.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = global_descriptor_set,
.dst_binding = 5,
.dst_array_element = 0,
.descriptor_type = .sampled_image,
.descriptor_infos = .{ .image = descriptor_images },
},
},
});
var chunks: std.AutoHashMapUnmanaged([3]i16, Chunk) = .empty;
errdefer {
var it = chunks.valueIterator();
while (it.next()) |chunk| {
chunk.deinit(descriptor_pool);
}
chunks.deinit(allocator_general);
}
const world_seed = engine.random.int(u64);
std.log.info("Using world seed 0x{x:0<16}", .{world_seed});
var it = Iterator2(i16).init(.{
.min = .{ -8, -8 },
.max = .{ 8, 8 },
});
while (it.next()) |chunk_coords2| {
const chunk_coords3 = chunk_coords2 ++ [_]i16{0};
const origin = vm.Vector3.init(
@floatFromInt(chunk_coords3[0]),
@floatFromInt(chunk_coords3[1]),
@floatFromInt(chunk_coords3[2]),
).mulScalar(16);
try chunks.ensureUnusedCapacity(allocator_general, 1);
chunks.putAssumeCapacityNoClobber(
chunk_coords3,
try Chunk.init(.{
.origin = origin,
.descriptor_pool = descriptor_pool,
.per_batch_descriptor_set_layout = per_batch_descriptor_set_layout,
}),
);
const chunk = chunks.getPtr(chunk_coords3).?;
var y: usize = 0;
while (y < 16) : (y += 1) {
const fpos0 = vm.Vector2x8.init(
.{ 0, 1, 2, 3, 4, 5, 6, 7 },
@splat(@floatFromInt(y)),
).add(.splat(origin.dropZ()));
const fpos1 = vm.Vector2x8.init(
.{ 8, 9, 10, 11, 12, 13, 14, 15 },
@splat(@floatFromInt(y)),
).add(.splat(origin.dropZ()));
const iheight0 = worldgen.heightIv(world_seed, fpos0);
const iheight1 = worldgen.heightIv(world_seed, fpos1);
const iheightv: [16]i32 = @as([8]i32, iheight0) ++ @as([8]i32, iheight1);
var x: usize = 0;
while (x < 16) : (x += 1) {
chunk.blocks[0][y][x] = block_bedrock;
const iheight = iheightv[x];
var i: i32 = 0;
while (i < iheight) : (i += 1) {
const iz = i + 1;
const block = if (i + 1 == iheight)
block_grass
else if (i + 4 >= iheight)
block_dirt
else
block_stone;
chunk.blocks[@intCast(iz)][y][x] = block;
}
}
}
}
const player_position_vx = vm.Vector3Int.init(0, 0, worldgen.heightI(world_seed, .zero) + 1);
const player_position_sv = player_position_vx
.mulScalar(c.sv_per_vx)
.add(.init(c.sv_per_vx / 2, c.sv_per_vx / 2, 0));
var chunk_it = chunks.iterator();
while (chunk_it.next()) |entry| {
const x, const y, const z = entry.key_ptr.*;
const chunk = entry.value_ptr;
try chunk.refresh(&blocks, .{
.positive_x = chunks.getPtr(.{ x + 1, y, z }),
.negative_x = chunks.getPtr(.{ x - 1, y, z }),
.positive_y = chunks.getPtr(.{ x, y + 1, z }),
.negative_y = chunks.getPtr(.{ x, y - 1, z }),
.positive_z = chunks.getPtr(.{ x, y, z + 1 }),
.negative_z = chunks.getPtr(.{ x, y, z - 1 }),
});
}
try point_lights.writeSlice(&point_lights_data);
try directional_lights.writeSlice(&directional_lights_data);
var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer);
errdefer skybox.deinit();
const font = &fonts.array.items[@intFromEnum(font_id)];
// TODO This coupling is not elegant - the game must provide the GUI with
// uniform buffer for globals. Maybe GUI should just use their own global
// uniform buffer not for any other reason than decoupling. After all, GUI
// only needs a single 3×2 matrix and does not need to depend on game's
// rendering pipeline.
try engine.updateDescriptorSets(.{
.writes = &.{
.{
.dst_set = gui.box_descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .uniform_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = global_uniforms.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .uniform_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = global_uniforms.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 2,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.glyph_data.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 3,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.control_points.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.text_descriptor_set,
.dst_binding = 4,
.dst_array_element = @intFromEnum(font_id),
.descriptor_type = .storage_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = font.band_data.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.image_descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .uniform_buffer,
.descriptor_infos = .{
.buffer = &.{
.{
.buffer = global_uniforms.buffer,
.offset = 0,
.range = vk.WHOLE_SIZE,
},
},
},
},
.{
.dst_set = gui.image_descriptor_set,
.dst_binding = 3,
.dst_array_element = 0,
.descriptor_type = .sampled_image,
.descriptor_infos = .{ .image = descriptor_images },
},
},
});
return .{
.global_descriptor_set_layout = global_descriptor_set_layout,
.per_batch_descriptor_set_layout = per_batch_descriptor_set_layout,
.descriptor_pool = descriptor_pool,
.global_descriptor_set = global_descriptor_set,
.pipeline_layout = pipeline_layout,
.pipeline = pipeline,
.vertex_buffer = vertex_buffer,
.index_buffer = index_buffer,
.global_uniforms = global_uniforms,
.global_uniforms_staging_buffer = global_uniforms_staging_buffer,
.global_uniforms_staging_ptr = global_uniforms_staging_ptr,
.global_uniforms_transfer_semaphores = global_uniforms_transfer_semaphores,
.point_lights = point_lights,
.directional_lights = directional_lights,
.sampler = sampler,
.deferred_command_buffers = try .initCapacity(allocator_general, 4),
.blocks = blocks,
.chunks = .{ .chunks = chunks },
.skybox = skybox,
.player = .init(player_position_sv, 0, 0),
.font = font_id,
};
}
pub fn deinit(self: *Game) void {
const allocator_general = ctx.allocator_general;
const engine = ctx.engine;
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
for (self.deferred_command_buffers.items) |*command_buffer| {
command_buffer.deinit();
}
self.deferred_command_buffers.deinit(allocator_general);
self.vertex_buffer.deinit();
self.index_buffer.deinit();
self.chunks.deinit(self.descriptor_pool);
self.skybox.deinit();
self.global_uniforms.deinit();
self.global_uniforms_staging_buffer.unmap();
self.global_uniforms_staging_buffer.deinit();
self.point_lights.deinit();
self.directional_lights.deinit();
for (self.global_uniforms_transfer_semaphores) |semaphore| {
engine.destroySemaphore(semaphore);
}
allocator_general.free(self.global_uniforms_transfer_semaphores);
engine.destroyDescriptorPool(self.descriptor_pool);
engine.destroySampler(self.sampler);
engine.destroyPipeline(self.pipeline);
engine.destroyPipelineLayout(self.pipeline_layout);
engine.destroyDescriptorSetLayout(self.per_batch_descriptor_set_layout);
engine.destroyDescriptorSetLayout(self.global_descriptor_set_layout);
self.blocks.deinit();
self.* = undefined;
}
pub fn update(self: *Game, dt: f32) void {
self.player.update(dt, self);
self.render() catch |err| {
std.log.err("Failed to render: {s}", .{@errorName(err)});
@panic("Frame update failed");
};
}
pub fn onKeyDown(self: *Game, key: glfw.Key) void {
self.player.onKeyDown(key);
}
pub fn onKeyUp(self: *Game, key: glfw.Key) void {
self.player.onKeyUp(key);
}
pub fn onMouseMove(self: *Game, dx: f32, dy: f32) void {
self.player.onMouseMove(dx, dy);
}
pub fn onMouseDown(self: *Game, button: glfw.MouseButton) void {
self.player.onMouseDown(button, self);
}
pub fn onMouseUp(self: *Game, button: glfw.MouseButton) void {
_ = self;
_ = button;
}
fn render(self: *Game) !void {
const allocator_general = ctx.allocator_general;
const window = ctx.window;
const swapchain = ctx.swapchain;
const gui = ctx.gui;
const framebuffer_width, const framebuffer_height = blk: {
const w, const h = window.getFramebufferSize();
break :blk [_]u32{ @intCast(w), @intCast(h) };
};
if (framebuffer_width == 0 or framebuffer_height == 0) {
return;
}
if (framebuffer_width != swapchain.extent.width or framebuffer_height != swapchain.extent.height) {
try self.recreateSwapchain();
}
if (swapchain.swapchain == .null_handle) {
return;
}
swapchain.acquire() catch |err| switch (err) {
error.OutOfDateKHR => return self.recreateSwapchain(),
else => return err,
};
for (self.deferred_command_buffers.items) |*deferred_command_buffer| {
deferred_command_buffer.deinit();
}
self.deferred_command_buffers.clearRetainingCapacity();
const extent = swapchain.extent;
const framebuffer_size = vm.Vector2.init(
@floatFromInt(extent.width),
@floatFromInt(extent.height),
);
const camera_position = self.player.position_sv
.asFloat()
.divScalar(c.sv_per_vx)
.add(.init(0, 0, Player.camera_height_vx));
const camera_rotation = vm.Quaternion.mulQuaternion(
.initRotation(.XY, self.player.yaw_turns),
.initRotation(.YZ, self.player.pitch_turns),
);
const camera_aspect_ratio = framebuffer_size.x / framebuffer_size.y;
const camera_yscale = 1.0 / @tan(0.5 * self.player.vertical_fov_deg * std.math.rad_per_deg);
const camera_xscale = camera_yscale / camera_aspect_ratio;
const matrix_ws_to_vs = vm.Matrix4x4
.initTranslationRotation(camera_position, camera_rotation)
.inverseOrthonormal();
// zig fmt: off
const matrix_vs_to_cs = vm.Matrix4x4.init(
camera_xscale, 0, 0, 0,
0, 0, 0, 1,
0, -camera_yscale, 0, 0,
0, 0, camera_near_plane, 0,
);
// zig fmt: on
// zig fmt: off
const matrix_sspx_to_cs = vm.Matrix3x2.init(
2.0 / framebuffer_size.x, 0,
0, 2.0 / framebuffer_size.y,
-1, -1,
);
// zig fmt: on
const ambient_light = vm.Vector3.init(0.01, 0.01, 0.01);
self.global_uniforms_staging_ptr.* = .{
.matrixWStoVS = .{
matrix_ws_to_vs.getIVersor().dropW(),
matrix_ws_to_vs.getJVersor().dropW(),
matrix_ws_to_vs.getKVersor().dropW(),
matrix_ws_to_vs.getTranslationVector().dropW(),
},
.matrixVStoCS = matrix_vs_to_cs,
.matrixSSPXtoCS = matrix_sspx_to_cs,
.pointLightCount = point_lights_data.len,
.directionalLightCount = directional_lights_data.len,
.ambientLight = ambient_light,
};
try self.global_uniforms_staging_buffer.flush(0, 1);
try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1);
const global_uniforms_transfer_command_buffer = try CommandBuffer.init(.transfer);
self.deferred_command_buffers.appendAssumeCapacity(global_uniforms_transfer_command_buffer);
try global_uniforms_transfer_command_buffer.beginCommandBuffer();
global_uniforms_transfer_command_buffer.copyBuffer(
self.global_uniforms_staging_buffer.buffer,
self.global_uniforms.buffer,
&.{
.{
.src_offset = 0,
.dst_offset = 0,
.size = @sizeOf(shaders.GlobalUniforms),
},
},
);
try global_uniforms_transfer_command_buffer.endCommandBuffer();
try global_uniforms_transfer_command_buffer.submit(.{
.signal_semaphores = &.{self.global_uniforms_transfer_semaphores[swapchain.image_index.?]},
});
try self.deferred_command_buffers.ensureUnusedCapacity(allocator_general, 1);
const command_buffer: CommandBuffer = try .init(.graphics);
self.deferred_command_buffers.appendAssumeCapacity(command_buffer);
try command_buffer.beginCommandBuffer();
{
const swapchain_image = swapchain.getCurrentSwapchainImage();
command_buffer.pipelineBarrier(.{
.src_stage_mask = .{ .color_attachment_output_bit = true },
.dst_stage_mask = .{ .color_attachment_output_bit = true },
.image_memory_barriers = &.{
.{
.src_access_mask = .{},
.dst_access_mask = .{
.color_attachment_read_bit = true,
.color_attachment_write_bit = true,
},
.old_layout = .undefined,
.new_layout = .color_attachment_optimal,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = swapchain_image.image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
},
},
});
command_buffer.pipelineBarrier(.{
.src_stage_mask = .{ .late_fragment_tests_bit = true },
.dst_stage_mask = .{ .early_fragment_tests_bit = true },
.image_memory_barriers = &.{
.{
.src_access_mask = .{
.depth_stencil_attachment_write_bit = true,
},
.dst_access_mask = .{
.depth_stencil_attachment_write_bit = true,
},
.old_layout = .undefined,
.new_layout = .depth_stencil_attachment_optimal,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = swapchain.depth_texture.?.image,
.subresource_range = .{
.aspect_mask = .{ .depth_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
},
},
});
try command_buffer.beginRendering(.{
.render_area = .{
.offset = .{ .x = 0, .y = 0 },
.extent = extent,
},
.layer_count = 1,
.view_mask = 0,
.color_attachments = &.{
.{
.image_view = swapchain_image.image_view,
.image_layout = .color_attachment_optimal,
.load_op = .clear,
.store_op = .store,
.clear_value = .{
.color = .{
.float_32 = .{ 0, 0, 0, 0 },
},
},
},
},
.depth_attachment = .{
.image_view = swapchain.depth_texture.?.image_view,
.image_layout = .depth_attachment_optimal,
.load_op = .clear,
.store_op = .dont_care,
.clear_value = .{
.depth_stencil = .{
.depth = 0,
.stencil = 0,
},
},
},
});
command_buffer.setViewport(0, .{
.x = 0,
.y = 0,
.width = @floatFromInt(extent.width),
.height = @floatFromInt(extent.height),
.min_depth = 0,
.max_depth = 1,
});
command_buffer.setScissor(0, .{
.offset = .{ .x = 0, .y = 0 },
.extent = extent,
});
// --- RENDER 3D SCENE ---
command_buffer.bindPipeline(.graphics, self.pipeline);
try command_buffer.bindVertexBuffer(0, .{
.buffer = self.vertex_buffer.buffer,
.offset = 0,
});
command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16);
command_buffer.bindDescriptorSet(.graphics, self.pipeline_layout, 0, self.global_descriptor_set, null);
var it = self.chunks.chunks.valueIterator();
while (it.next()) |chunk| {
chunk.draw(self.pipeline_layout, command_buffer);
}
try self.skybox.draw(command_buffer);
// --- RENDER GUI ---
try gui.draw(command_buffer);
// --- FINALIZE ---
command_buffer.endRendering();
command_buffer.pipelineBarrier(.{
.src_stage_mask = .{ .color_attachment_output_bit = true },
.dst_stage_mask = .{ .bottom_of_pipe_bit = true },
.image_memory_barriers = &.{
.{
.src_access_mask = .{ .color_attachment_write_bit = true },
.dst_access_mask = .{},
.old_layout = .color_attachment_optimal,
.new_layout = .present_src_khr,
.src_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED,
.image = swapchain_image.image,
.subresource_range = .{
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
.layer_count = 1,
},
},
},
});
}
try command_buffer.endCommandBuffer();
swapchain.present(.{
.command_buffer = command_buffer,
.wait_semaphores = &.{
.{
.semaphore = self.global_uniforms_transfer_semaphores[swapchain.image_index.?],
.stage_flags = .{ .vertex_shader_bit = true },
},
},
}) catch |err| switch (err) {
error.OutOfDateKHR => return self.recreateSwapchain(),
else => return err,
};
}
fn recreateSwapchain(self: *Game) !void {
const engine = ctx.engine;
const swapchain = ctx.swapchain;
try swapchain.recreate();
for (self.global_uniforms_transfer_semaphores, 0..) |*semaphore, i| {
engine.destroySemaphore(semaphore.*);
semaphore.* = try engine.createSemaphore();
engine.setObjectName(semaphore.*, "S Transfer[{d}]", .{i});
}
}