Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/engine/graphics/rhi_vulkan.zig
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ const TextureResource = struct {
height: u32,
};

const ZombieBuffer = struct {
buffer: c.VkBuffer,
memory: c.VkDeviceMemory,
};

/// Core Vulkan context containing all renderer state.
/// Owns Vulkan objects and manages their lifecycle.
const VulkanContext = struct {
Expand All @@ -109,6 +114,8 @@ const VulkanContext = struct {
transfer_command_pool: c.VkCommandPool,
transfer_command_buffer: c.VkCommandBuffer,

buffer_deletion_queue: [MAX_FRAMES_IN_FLIGHT]std.ArrayListUnmanaged(ZombieBuffer),

// Sync
image_available_semaphores: [MAX_FRAMES_IN_FLIGHT]c.VkSemaphore,
render_finished_semaphores: [MAX_FRAMES_IN_FLIGHT]c.VkSemaphore,
Expand Down Expand Up @@ -1347,6 +1354,10 @@ fn init(ctx_ptr: *anyopaque, allocator: std.mem.Allocator) anyerror!void {
ctx.shadow_image_layouts[si] = c.VK_IMAGE_LAYOUT_UNDEFINED;
}

for (0..MAX_FRAMES_IN_FLIGHT) |frame_i| {
ctx.buffer_deletion_queue[frame_i] = .empty;
}

std.log.info("Vulkan initialized successfully!", .{});
}

Expand Down Expand Up @@ -1438,6 +1449,14 @@ fn deinit(ctx_ptr: *anyopaque) void {
}
ctx.textures.deinit();

for (0..MAX_FRAMES_IN_FLIGHT) |frame_i| {
for (ctx.buffer_deletion_queue[frame_i].items) |zombie| {
if (zombie.buffer != null) c.vkDestroyBuffer(ctx.device, zombie.buffer, null);
if (zombie.memory != null) c.vkFreeMemory(ctx.device, zombie.memory, null);
}
ctx.buffer_deletion_queue[frame_i].deinit(ctx.allocator);
}

c.vkDestroyDevice(ctx.device, null);
}
if (ctx.surface != null) c.vkDestroySurfaceKHR(ctx.instance, ctx.surface, null);
Expand Down Expand Up @@ -1549,8 +1568,9 @@ fn destroyBuffer(ctx_ptr: *anyopaque, handle: rhi.BufferHandle) void {
ctx.mutex.unlock();

if (entry_opt) |entry| {
c.vkDestroyBuffer(ctx.device, entry.value.buffer, null);
c.vkFreeMemory(ctx.device, entry.value.memory, null);
ctx.buffer_deletion_queue[ctx.current_sync_frame].append(ctx.allocator, .{ .buffer = entry.value.buffer, .memory = entry.value.memory }) catch {
std.log.err("Failed to queue buffer deletion (OOM). Leaking buffer.", .{});
};
}
}

Expand Down Expand Up @@ -1762,6 +1782,13 @@ fn beginFrame(ctx_ptr: *anyopaque) void {

_ = c.vkWaitForFences(ctx.device, 1, &fence, c.VK_TRUE, std.math.maxInt(u64));

// Process deletion queue for this frame
for (ctx.buffer_deletion_queue[ctx.current_sync_frame].items) |zombie| {
c.vkDestroyBuffer(ctx.device, zombie.buffer, null);
c.vkFreeMemory(ctx.device, zombie.memory, null);
}
ctx.buffer_deletion_queue[ctx.current_sync_frame].clearRetainingCapacity();

var image_index: u32 = 0;
const result = c.vkAcquireNextImageKHR(ctx.device, ctx.swapchain, 1000000000, acquire_semaphore, null, &image_index);

Expand Down
123 changes: 13 additions & 110 deletions src/game/app.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const AppState = @import("state.zig").AppState;
const Settings = @import("state.zig").Settings;
const Menus = @import("menus.zig");
const RenderSystem = @import("render_system.zig").RenderSystem;
const MapController = @import("map_controller.zig").MapController;

pub const App = struct {
allocator: std.mem.Allocator,
Expand All @@ -51,14 +52,7 @@ pub const App = struct {

world: ?*World,
world_map: ?WorldMap,
show_map: bool,
map_needs_update: bool,
map_zoom: f32,
map_target_zoom: f32,
map_pos_x: f32,
map_pos_z: f32,
last_mouse_x: f32,
last_mouse_y: f32,
map_controller: MapController,

pub fn init(allocator: std.mem.Allocator) !*App {
var use_vulkan = false;
Expand Down Expand Up @@ -114,14 +108,7 @@ pub const App = struct {
.seed_focused = false,
.world = null,
.world_map = null,
.show_map = false,
.map_needs_update = true,
.map_zoom = 4.0,
.map_target_zoom = 4.0,
.map_pos_x = 0.0,
.map_pos_z = 0.0,
.last_mouse_x = 0.0,
.last_mouse_y = 0.0,
.map_controller = .{},
};

return app;
Expand Down Expand Up @@ -163,8 +150,8 @@ pub const App = struct {
continue;
};
if (self.world_map == null) self.world_map = WorldMap.init(self.render_system.rhi, 256, 256);
self.show_map = false;
self.map_needs_update = true;
self.map_controller.show_map = false;
self.map_controller.map_needs_update = true;
self.camera = Camera.init(.{ .position = Vec3.init(8, 100, 8), .pitch = -0.3, .move_speed = 50.0 });
}

Expand All @@ -183,8 +170,8 @@ pub const App = struct {
const mouse_clicked = self.input.isMouseButtonPressed(.left);

if (self.input.isKeyPressed(.escape)) {
if (self.show_map) {
self.show_map = false;
if (self.map_controller.show_map) {
self.map_controller.show_map = false;
if (self.app_state == .world) self.input.setMouseCapture(self.window_manager.window, true);
} else {
switch (self.app_state) {
Expand Down Expand Up @@ -227,77 +214,11 @@ pub const App = struct {
self.render_system.rhi.setVSync(self.settings.vsync);
}
if (self.input.isKeyPressed(.u)) self.debug_shadows = !self.debug_shadows;
if (self.input.isKeyPressed(.m)) {
self.show_map = !self.show_map;
log.log.info("Toggle map: show={}", .{self.show_map});
if (self.show_map) {
self.map_pos_x = self.camera.position.x;
self.map_pos_z = self.camera.position.z;
self.map_target_zoom = self.map_zoom;
self.map_needs_update = true;
self.input.setMouseCapture(self.window_manager.window, false);
} else if (self.app_state == .world) self.input.setMouseCapture(self.window_manager.window, true);
}

if (self.show_map) {
const dt = @min(self.time.delta_time, 0.033);
// ... map input logic (omitted for brevity, same as before) ...
// Wait, I need to keep this logic or extract it.
// For now, I'll copy-paste the map input logic as it's coupled to App state.
if (self.input.isKeyDown(.plus) or self.input.isKeyDown(.kp_plus)) {
self.map_target_zoom /= @exp(1.2 * dt);
self.map_needs_update = true;
}
if (self.input.isKeyDown(.minus) or self.input.isKeyDown(.kp_minus)) {
self.map_target_zoom *= @exp(1.2 * dt);
self.map_needs_update = true;
}
if (self.input.scroll_y != 0) {
self.map_target_zoom *= @exp(-self.input.scroll_y * 0.12);
self.map_needs_update = true;
}
self.map_target_zoom = std.math.clamp(self.map_target_zoom, 0.05, 128.0);
const old_zoom = self.map_zoom;
self.map_zoom = std.math.lerp(self.map_zoom, self.map_target_zoom, 20.0 * dt);
if (@abs(self.map_zoom - old_zoom) > 0.001 * self.map_zoom) self.map_needs_update = true;
if (self.input.isKeyPressed(.space)) {
self.map_pos_x = self.camera.position.x;
self.map_pos_z = self.camera.position.z;
self.map_needs_update = true;
}
const map_ui_size: f32 = @min(screen_w, screen_h) * 0.8;
const world_to_screen_ratio = if (self.world_map) |m| @as(f32, @floatFromInt(m.width)) / map_ui_size else 1.0;
if (self.input.isMouseButtonPressed(.left)) {
self.last_mouse_x = mouse_x;
self.last_mouse_y = mouse_y;
}
if (self.input.isMouseButtonDown(.left)) {
const drag_dx = mouse_x - self.last_mouse_x;
const drag_dz = mouse_y - self.last_mouse_y;
if (@abs(drag_dx) > 0.1 or @abs(drag_dz) > 0.1) {
self.map_pos_x -= drag_dx * self.map_zoom * world_to_screen_ratio;
self.map_pos_z -= drag_dz * self.map_zoom * world_to_screen_ratio;
self.map_needs_update = true;
}
self.last_mouse_x = mouse_x;
self.last_mouse_y = mouse_y;
} else {
const pan_kb_speed = 800.0 * self.map_zoom;
var dx: f32 = 0;
var dz: f32 = 0;
if (self.input.isKeyDown(.w)) dz -= 1;
if (self.input.isKeyDown(.s)) dz += 1;
if (self.input.isKeyDown(.a)) dx -= 1;
if (self.input.isKeyDown(.d)) dx += 1;
if (dx != 0 or dz != 0) {
self.map_pos_x += dx * pan_kb_speed * dt;
self.map_pos_z += dz * pan_kb_speed * dt;
self.map_needs_update = true;
}
}
}
self.map_controller.update(&self.input, &self.camera, self.time.delta_time, self.window_manager.window, screen_w, screen_h, if (self.world_map) |m| m.width else 256);

if (self.debug_shadows and self.input.isKeyPressed(.k)) self.debug_cascade_idx = (self.debug_cascade_idx + 1) % 3;

if (self.input.isKeyPressed(.@"1")) if (self.render_system.atmosphere) |*a| a.setTimeOfDay(0.0);
if (self.input.isKeyPressed(.@"2")) if (self.render_system.atmosphere) |*a| a.setTimeOfDay(0.25);
if (self.input.isKeyPressed(.@"3")) if (self.render_system.atmosphere) |*a| a.setTimeOfDay(0.5);
Expand All @@ -307,7 +228,7 @@ pub const App = struct {
};

if (in_world) {
if (!self.show_map and !in_pause) {
if (!self.map_controller.show_map and !in_pause) {
self.camera.update(&self.input, self.time.delta_time);
}

Expand Down Expand Up @@ -466,27 +387,9 @@ pub const App = struct {
}
if (self.ui) |*u| {
u.begin();
if (self.show_map) if (self.world_map) |*m| {
if (self.map_needs_update) {
try m.update(&active_world.generator, self.map_pos_x, self.map_pos_z, self.map_zoom);
self.map_needs_update = false;
}
const sz: f32 = @min(screen_w, screen_h) * 0.8;
const mx = (screen_w - sz) * 0.5;
const my = (screen_h - sz) * 0.5;
u.drawRect(.{ .x = 0, .y = 0, .width = screen_w, .height = screen_h }, Color.rgba(0, 0, 0, 0.5));
u.drawTexture(@intCast(m.texture.handle), .{ .x = mx, .y = my, .width = sz, .height = sz });
u.drawRectOutline(.{ .x = mx, .y = my, .width = sz, .height = sz }, Color.white, 2.0);
Font.drawTextCentered(u, "WORLD MAP", screen_w * 0.5, my - 40.0, 3.0, Color.white);
const rx = (self.camera.position.x - self.map_pos_x) / (self.map_zoom * @as(f32, @floatFromInt(m.width)));
const rz = (self.camera.position.z - self.map_pos_z) / (self.map_zoom * @as(f32, @floatFromInt(m.height)));
const px = mx + (rx + 0.5) * sz;
const pz = my + (rz + 0.5) * sz;
if (px >= mx and px <= mx + sz and pz >= my and pz <= my + sz) {
u.drawRect(.{ .x = px - 5, .y = pz - 1, .width = 10, .height = 2 }, Color.red);
u.drawRect(.{ .x = px - 1, .y = pz - 5, .width = 2, .height = 10 }, Color.red);
}
};
if (self.world_map) |*m| {
try self.map_controller.draw(u, screen_w, screen_h, m, &active_world.generator, self.camera.position);
}
u.drawRect(.{ .x = 10, .y = 10, .width = 80, .height = 30 }, Color.rgba(0, 0, 0, 0.7));
Font.drawNumber(u, @intFromFloat(self.time.fps), 15, 15, Color.white);
const stats = active_world.getStats();
Expand Down
128 changes: 128 additions & 0 deletions src/game/map_controller.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const std = @import("std");
const c = @import("../c.zig").c;
const Input = @import("../engine/input/input.zig").Input;
const WorldMap = @import("../world/worldgen/world_map.zig").WorldMap;
const Camera = @import("../engine/graphics/camera.zig").Camera;
const TerrainGenerator = @import("../world/worldgen/generator.zig").TerrainGenerator;
const UISystem = @import("../engine/ui/ui_system.zig").UISystem;
const Color = @import("../engine/ui/ui_system.zig").Color;
const Font = @import("../engine/ui/font.zig");
const log = @import("../engine/core/log.zig");
const Vec3 = @import("../engine/math/vec3.zig").Vec3;

pub const MapController = struct {
show_map: bool = false,
map_needs_update: bool = true,
map_zoom: f32 = 4.0,
map_target_zoom: f32 = 4.0,
map_pos_x: f32 = 0.0,
map_pos_z: f32 = 0.0,
last_mouse_x: f32 = 0.0,
last_mouse_y: f32 = 0.0,

pub fn update(self: *MapController, input: *Input, camera: *const Camera, time_delta: f32, window: *c.SDL_Window, screen_w: f32, screen_h: f32, world_map_width: u32) void {
if (input.isKeyPressed(.m)) {
self.show_map = !self.show_map;
log.log.info("Toggle map: show={}", .{self.show_map});
if (self.show_map) {
self.map_pos_x = camera.position.x;
self.map_pos_z = camera.position.z;
self.map_target_zoom = self.map_zoom;
self.map_needs_update = true;
input.setMouseCapture(window, false);
} else {
input.setMouseCapture(window, true);
}
}

if (!self.show_map) return;

const dt = @min(time_delta, 0.033);
if (input.isKeyDown(.plus) or input.isKeyDown(.kp_plus)) {
self.map_target_zoom /= @exp(1.2 * dt);
self.map_needs_update = true;
}
if (input.isKeyDown(.minus) or input.isKeyDown(.kp_minus)) {
self.map_target_zoom *= @exp(1.2 * dt);
self.map_needs_update = true;
}
if (input.scroll_y != 0) {
self.map_target_zoom *= @exp(-input.scroll_y * 0.12);
self.map_needs_update = true;
}
self.map_target_zoom = std.math.clamp(self.map_target_zoom, 0.05, 128.0);
const old_zoom = self.map_zoom;
self.map_zoom = std.math.lerp(self.map_zoom, self.map_target_zoom, 20.0 * dt);
if (@abs(self.map_zoom - old_zoom) > 0.001 * self.map_zoom) self.map_needs_update = true;

if (input.isKeyPressed(.space)) {
self.map_pos_x = camera.position.x;
self.map_pos_z = camera.position.z;
self.map_needs_update = true;
}

const mouse_pos = input.getMousePosition();
const mouse_x: f32 = @floatFromInt(mouse_pos.x);
const mouse_y: f32 = @floatFromInt(mouse_pos.y);

const map_ui_size: f32 = @min(screen_w, screen_h) * 0.8;
const world_to_screen_ratio = @as(f32, @floatFromInt(world_map_width)) / map_ui_size;

if (input.isMouseButtonPressed(.left)) {
self.last_mouse_x = mouse_x;
self.last_mouse_y = mouse_y;
}

if (input.isMouseButtonDown(.left)) {
const drag_dx = mouse_x - self.last_mouse_x;
const drag_dz = mouse_y - self.last_mouse_y;
if (@abs(drag_dx) > 0.1 or @abs(drag_dz) > 0.1) {
self.map_pos_x -= drag_dx * self.map_zoom * world_to_screen_ratio;
self.map_pos_z -= drag_dz * self.map_zoom * world_to_screen_ratio;
self.map_needs_update = true;
}
self.last_mouse_x = mouse_x;
self.last_mouse_y = mouse_y;
} else {
const pan_kb_speed = 800.0 * self.map_zoom;
var dx: f32 = 0;
var dz: f32 = 0;
if (input.isKeyDown(.w)) dz -= 1;
if (input.isKeyDown(.s)) dz += 1;
if (input.isKeyDown(.a)) dx -= 1;
if (input.isKeyDown(.d)) dx += 1;
if (dx != 0 or dz != 0) {
self.map_pos_x += dx * pan_kb_speed * dt;
self.map_pos_z += dz * pan_kb_speed * dt;
self.map_needs_update = true;
}
}
}

pub fn draw(self: *MapController, u: *UISystem, screen_w: f32, screen_h: f32, world_map: *WorldMap, generator: *const TerrainGenerator, camera_pos: Vec3) !void {
if (!self.show_map) return;

if (self.map_needs_update) {
try world_map.update(generator, self.map_pos_x, self.map_pos_z, self.map_zoom);
self.map_needs_update = false;
}

const sz: f32 = @min(screen_w, screen_h) * 0.8;
const mx = (screen_w - sz) * 0.5;
const my = (screen_h - sz) * 0.5;
u.drawRect(.{ .x = 0, .y = 0, .width = screen_w, .height = screen_h }, Color.rgba(0, 0, 0, 0.5));
u.drawTexture(@intCast(world_map.texture.handle), .{ .x = mx, .y = my, .width = sz, .height = sz });
u.drawRectOutline(.{ .x = mx, .y = my, .width = sz, .height = sz }, Color.white, 2.0);
Font.drawTextCentered(u, "WORLD MAP", screen_w * 0.5, my - 40.0, 3.0, Color.white);

const rx = (camera_pos.x - self.map_pos_x) / (self.map_zoom * @as(f32, @floatFromInt(world_map.width)));
const rz = (camera_pos.z - self.map_pos_z) / (self.map_zoom * @as(f32, @floatFromInt(world_map.height)));
const px = mx + (rx + 0.5) * sz;
const pz = my + (rz + 0.5) * sz;

if (px >= mx and px <= mx + sz and pz >= my and pz <= my + sz) {
u.drawRect(.{ .x = px - 5, .y = pz - 1, .width = 10, .height = 2 }, Color.red);
u.drawRect(.{ .x = px - 1, .y = pz - 5, .width = 2, .height = 10 }, Color.red);
}
}
};