diff --git a/build.zig b/build.zig index 5a074da..22b1948 100644 --- a/build.zig +++ b/build.zig @@ -77,9 +77,11 @@ pub fn build(b: *std.Build) void { if (wayland) { exe.root_module.addIncludePath(b.path("ext")); exe.linkSystemLibrary("wayland-client"); + exe.linkSystemLibrary("xkbcommon"); exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") }); } else { exe.linkSystemLibrary("xcb"); + exe.linkSystemLibrary("xcb-keysyms"); exe.linkSystemLibrary("xcb-icccm"); } b.installArtifact(exe); diff --git a/src/ecs/Input.zig b/src/ecs/Input.zig index 820c0ab..dc6f46f 100644 --- a/src/ecs/Input.zig +++ b/src/ecs/Input.zig @@ -22,32 +22,32 @@ pub const KeyCode = enum(u32) { @"9" = 57, semicolon = 59, equal = 61, - a = 65, - b = 66, - c = 67, - d = 68, - e = 69, - f = 70, - g = 71, - h = 72, - i = 73, - j = 74, - k = 75, - l = 76, - m = 77, - n = 78, - o = 79, - p = 80, - q = 81, - r = 82, - s = 83, - t = 84, - u = 85, - v = 86, - w = 87, - x = 88, - y = 89, - z = 90, + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, left_bracket = 91, backslash = 92, right_bracket = 93, @@ -124,6 +124,7 @@ pub const KeyCode = enum(u32) { right_alt = 346, right_super = 347, menu = 348, + _, }; key_pressed: [@intFromEnum(KeyCode.menu)]bool = .{false} ** @intFromEnum(Input.KeyCode.menu), diff --git a/src/ecs/entities.zig b/src/ecs/entities.zig index 63814b1..3bf5599 100644 --- a/src/ecs/entities.zig +++ b/src/ecs/entities.zig @@ -13,10 +13,10 @@ pub const SystemGroup = []const System; pub const SyncGroup = []const System; pub const Resources = struct { - camera: Camera, + camera: *Camera, renderer: *Renderer, - input: Input, - delta_time: f64 = 0.0, + input: *Input, + delta_time: f32 = 0.0, }; pub const Human = struct { diff --git a/src/math.zig b/src/math.zig index 09c8e99..ad93c0c 100644 --- a/src/math.zig +++ b/src/math.zig @@ -47,8 +47,7 @@ pub const Matrix = extern struct { pub fn lookAt(eye: [3]f32, target: [3]f32, arbitrary_up: [3]f32) Matrix { const t: @Vector(3, f32) = target; - var e: @Vector(3, f32) = eye; - e = -e; + const e: @Vector(3, f32) = eye; const u: @Vector(3, f32) = arbitrary_up; const forward = normalize(t - e); const right = normalize(cross(forward, u)); diff --git a/src/rendering/Camera.zig b/src/rendering/Camera.zig index 18d06b1..de5dced 100644 --- a/src/rendering/Camera.zig +++ b/src/rendering/Camera.zig @@ -12,32 +12,39 @@ pub const Uniform = struct { }; position: @Vector(3, f32), -target: @Vector(3, f32) = .{ 0.0, 0.0, 0.0 }, -front: @Vector(3, f32) = .{ 0.0, 0.0, 1.0 }, +target: @Vector(3, f32) = .{ 0.0, 0.0, -1.0 }, up: @Vector(3, f32) = .{ 0.0, 1.0, 0.0 }, -speed: f32 = 2.5, +speed: f32 = 5, pub fn getProjection(width: usize, height: usize) math.Matrix { return math.Matrix.perspective(math.rad(45.0), (@as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height))), 0.1, 100.0); } -pub fn getView(self: Camera) math.Matrix { - return math.Matrix.lookAt(self.position, self.target, self.up); +pub fn getView(self: *Camera) math.Matrix { + return math.Matrix.lookAt(self.position, self.position + self.target, self.up); } -pub fn moveCamera(pool: *ecs.Pool) void { +pub fn moveCamera(pool: *ecs.Pool) !void { const input = pool.resources.input; - const camera = pool.resources.camera; + var camera = pool.resources.camera; + const mul = @as(@Vector(3, f32), @splat(camera.speed * pool.resources.delta_time)); + if (input.isKeyDown(.w)) { - camera.position += (camera.front * (camera.speed * pool.resources.delta_time)); + camera.position += camera.target * mul; } if (input.isKeyDown(.s)) { - camera.position -= (camera.front * (camera.speed * pool.resources.delta_time)); + camera.position -= camera.target * mul; } if (input.isKeyDown(.a)) { - camera.position -= math.normalize(math.cross(camera.front, camera.up)) * (camera.speed * pool.resources.delta_time); + camera.position -= math.normalize(math.cross(camera.target, camera.up)) * mul; } if (input.isKeyDown(.d)) { - camera.position += math.normalize(math.cross(camera.front, camera.up)) * (camera.speed * pool.resources.delta_time); + camera.position += math.normalize(math.cross(camera.target, camera.up)) * mul; + } + if (input.isKeyDown(.space)) { + camera.position += camera.up * mul; + } + if (input.isKeyDown(.left_shift)) { + camera.position -= camera.up * mul; } } diff --git a/src/rendering/PhysicalDevice.zig b/src/rendering/PhysicalDevice.zig index dcf95dd..3fd893e 100644 --- a/src/rendering/PhysicalDevice.zig +++ b/src/rendering/PhysicalDevice.zig @@ -185,6 +185,7 @@ pub fn create_device(self: *PhysicalDevice, surface: vk.Surface, allocator: Allo samples = 2; } + std.debug.print("Using {} samples for MSAA\n", .{samples}); return .{ diff --git a/src/rendering/Renderer.zig b/src/rendering/Renderer.zig index 4e72a52..9b5f9ad 100644 --- a/src/rendering/Renderer.zig +++ b/src/rendering/Renderer.zig @@ -109,10 +109,12 @@ pub fn render(pool: *ecs.Pool) anyerror!void { const now = try std.time.Instant.now(); const delta_time: f32 = @as(f32, @floatFromInt(now.since(renderer.previous_time))) / @as(f32, 1_000_000_000.0); + pool.resources.delta_time = delta_time; renderer.previous_time = now; + const view = camera.getView(); const view_memory = renderer.graphics_pipeline.view_memory; - @memcpy(view_memory[0..@sizeOf(math.Matrix)], std.mem.asBytes(&camera.getView())); + @memcpy(view_memory[0..@sizeOf(math.Matrix)], std.mem.asBytes(&view)); const view_pos_memory = renderer.graphics_pipeline.view_pos_memory; const view_pos: [*]f32 = @alignCast(@ptrCast(view_pos_memory)); @@ -120,8 +122,6 @@ pub fn render(pool: *ecs.Pool) anyerror!void { view_pos[1] = camera.position[1]; view_pos[2] = camera.position[2]; - _ = delta_time; - const transform_memory = renderer.graphics_pipeline.transform_buffer.mapped_memory; try renderer.device.waitFence(renderer.current_frame); diff --git a/src/sideros.zig b/src/sideros.zig index 16ec8f9..b7738a2 100644 --- a/src/sideros.zig +++ b/src/sideros.zig @@ -14,6 +14,10 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); var pool: ecs.Pool = undefined; var renderer: Renderer = undefined; +var camera: rendering.Camera = .{ + .position = .{ 0.0, 0.0, 5.0 }, +}; +var input: ecs.Input = .{ .key_pressed = .{false} ** @intFromEnum(ecs.Input.KeyCode.menu) }; var resources: ecs.Resources = undefined; fn init_mods() void { @@ -47,18 +51,15 @@ fn init_mods() void { export fn sideros_init(init: api.GameInit) callconv(.c) void { resources = .{ - .camera = .{ - .position = .{ 0.0, 5.0, -5.0 }, - .target = .{ 0.0, 0.0, 0.0 }, - }, + .camera = &camera, .renderer = undefined, - .input = .{ .key_pressed = .{false} ** @intFromEnum(ecs.Input.KeyCode.menu) }, + .input = &input, }; pool = ecs.Pool.init(allocator, &resources) catch @panic("TODO: Gracefully handle error"); // TODO(ernesto): I think this @ptrCast are unavoidable but maybe not? renderer = Renderer.init(allocator, @ptrCast(init.instance), @ptrCast(init.surface)) catch @panic("TODO: Gracefully handle error"); - pool.addSystemGroup(&[_]ecs.System{Renderer.render}, true) catch @panic("TODO: Gracefuly handle error"); + pool.addSystemGroup(&[_]ecs.System{Renderer.render, rendering.Camera.moveCamera}, true) catch @panic("TODO: Gracefuly handle error"); pool.resources.renderer = &renderer; pool.tick(); init_mods(); @@ -74,3 +75,13 @@ export fn sideros_cleanup() callconv(.c) void { pool.deinit(); if (gpa.deinit() != .ok) @panic("Memory leaked"); } + +export fn sideros_key_callback(key: u32, release: bool) callconv(.c) void { + if (key <= @intFromEnum(ecs.Input.KeyCode.menu) and key >= @intFromEnum(ecs.Input.KeyCode.space)) { + if (release) { + input.key_pressed[key] = false; + } else { + input.key_pressed[key] = true; + } + } +} diff --git a/src/sideros_api.h b/src/sideros_api.h index c07091e..93e90f9 100644 --- a/src/sideros_api.h +++ b/src/sideros_api.h @@ -1,14 +1,16 @@ #include "vulkan/vulkan.h" +#include typedef struct { - VkInstance instance; - VkSurfaceKHR surface; + VkInstance instance; + VkSurfaceKHR surface; } GameInit; typedef struct { - double dt; + double dt; } GameUpdate; void sideros_init(GameInit init); void sideros_update(GameUpdate state); +void sideros_key_callback(unsigned int key, bool release); void sideros_cleanup(void); diff --git a/src/wayland.zig b/src/wayland.zig index ab29cee..e926e70 100644 --- a/src/wayland.zig +++ b/src/wayland.zig @@ -2,6 +2,7 @@ const std = @import("std"); const c = @cImport({ @cInclude("wayland-client.h"); @cInclude("xdg-shell.h"); + @cInclude("xkbcommon/xkbcommon.h"); @cInclude("vulkan/vulkan.h"); @cInclude("vulkan/vulkan_wayland.h"); }); @@ -16,11 +17,22 @@ var quit = false; var new_width: u32 = 0; var new_height: u32 = 0; +fn mapKeysym(keysym: u32) u32 { + return switch (keysym) { + 0xffe1 => 340, + else => keysym, + }; +} + const State = struct { compositor: ?*c.wl_compositor = null, shell: ?*c.xdg_wm_base = null, surface: ?*c.wl_surface = null, + seat: ?*c.wl_seat = null, configured: bool = false, + xkb_context: ?*c.xkb_context = null, + xkb_state: ?*c.xkb_state = null, + allocator: std.mem.Allocator, }; const validation_layers: []const [*c]const u8 = if (!debug) &[0][*c]const u8{} else &[_][*c]const u8{ @@ -156,6 +168,8 @@ fn registryHandleGlobal(data: ?*anyopaque, registry: ?*c.wl_registry, name: u32, } else if (std.mem.eql(u8, @as([:0]const u8, std.mem.span(interface)), std.mem.span(c.xdg_wm_base_interface.name))) { state.shell = @ptrCast(c.wl_registry_bind(registry.?, name, &c.xdg_wm_base_interface, 4)); _ = c.xdg_wm_base_add_listener(state.shell, &shell_listener, null); + } else if (std.mem.eql(u8, @as([:0]const u8, std.mem.span(interface)), std.mem.span(c.wl_seat_interface.name))) { + state.seat = @ptrCast(c.wl_registry_bind(registry.?, name, &c.wl_seat_interface, 4)); } } @@ -216,6 +230,62 @@ fn frameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, time: u32) call _ = c.wl_surface_commit(state.surface); } +fn keyboardHandleKeymap(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, format: u32, fd: i32, size: u32) callconv(.c) void { + _ = keyboard; + _ = format; + + const state: *State = @alignCast(@ptrCast(data)); + + const addr = std.posix.mmap(null, size, std.posix.PROT.READ, std.os.linux.MAP { .TYPE = .PRIVATE }, fd, 0) catch @panic("Can't mmap keymap data"); + const mapped: []u8 = @as([*]u8, @ptrCast(addr))[0..size]; + + const keymap = c.xkb_keymap_new_from_string(state.xkb_context, @ptrCast(mapped), c.XKB_KEYMAP_FORMAT_TEXT_V1, c.XKB_KEYMAP_COMPILE_NO_FLAGS); + state.xkb_state = c.xkb_state_new(keymap); +} + +fn keyboardHandleEnter(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface, keys: ?*c.wl_array) callconv(.c) void { + _ = data; + _ = keyboard; + _ = serial; + _ = surface; + _ = keys; +} + +fn keyboardHandleLeave(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, surface: ?*c.wl_surface) callconv(.c) void { + _ = data; + _ = keyboard; + _ = serial; + _ = surface; +} + +fn keyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, time: u32, key: u32, s: u32) callconv(.c) void { + _ = keyboard; + _ = serial; + _ = time; + + const state: *State = @alignCast(@ptrCast(data)); + + const keysym = c.xkb_state_key_get_one_sym(state.xkb_state, key+8); + sideros.sideros_key_callback(mapKeysym(keysym), s == 0); +} + +fn keyboardHandleModifiers(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32, mods_depressed: u32, mods_latched: u32, mods_locked: u32, group: u32) callconv(.c) void { + _ = data; + _ = keyboard; + _ = serial; + _ = mods_depressed; + _ = mods_latched; + _ = mods_locked; + _ = group; +} + +fn keyboardHandleRepeatInfo(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, rate: i32, delay: i32) callconv(.c) void { + _ = data; + _ = keyboard; + _ = rate; + _ = delay; +} + const frame_listener: c.wl_callback_listener = .{ .done = frameHandleDone, }; @@ -239,8 +309,22 @@ const registry_listener: c.wl_registry_listener = .{ .global_remove = registryHandleGlobalRemove, }; +const keyboard_listener: c.wl_keyboard_listener = .{ + .keymap = keyboardHandleKeymap, + .enter = keyboardHandleEnter, + .leave = keyboardHandleLeave, + .key = keyboardHandleKey, + .modifiers = keyboardHandleModifiers, + .repeat_info = keyboardHandleRepeatInfo, +}; + pub fn main() !void { - var state: State = .{}; + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer if (gpa.deinit() != .ok) @panic("Platform memory leaked"); + + var state: State = .{ .allocator = allocator }; + state.xkb_context = c.xkb_context_new(c.XKB_CONTEXT_NO_FLAGS); const display = c.wl_display_connect(null); defer c.wl_display_disconnect(display); if (display == null) { @@ -251,6 +335,9 @@ pub fn main() !void { _ = c.wl_registry_add_listener(registry, ®istry_listener, @ptrCast(&state)); _ = c.wl_display_roundtrip(display); + const keyboard = c.wl_seat_get_keyboard(state.seat); + _ = c.wl_keyboard_add_listener(keyboard, &keyboard_listener, @ptrCast(&state)); + const surface = c.wl_compositor_create_surface(state.compositor); const xdg_surface = c.xdg_wm_base_get_xdg_surface(state.shell, surface); _ = c.xdg_surface_add_listener(xdg_surface, &surface_listener, @ptrCast(&state)); @@ -272,9 +359,6 @@ pub fn main() !void { } - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - defer if (gpa.deinit() != .ok) @panic("Platform memory leaked"); const gameInit = try vulkan_init(allocator, display, surface); defer vulkan_cleanup(gameInit); diff --git a/src/xorg.zig b/src/xorg.zig index 6b9b4ba..2626e28 100644 --- a/src/xorg.zig +++ b/src/xorg.zig @@ -4,6 +4,7 @@ const sideros = @cImport({ }); const c = @cImport({ @cInclude("xcb/xcb.h"); + @cInclude("xcb/xcb_keysyms.h"); @cInclude("vulkan/vulkan.h"); @cInclude("vulkan/vulkan_xcb.h"); @cInclude("xcb/xcb_icccm.h"); @@ -138,13 +139,15 @@ fn vulkan_cleanup(gameInit: sideros.GameInit) void { pub fn main() !void { const connection = c.xcb_connect(null, null); defer c.xcb_disconnect(connection); + const keysyms = c.xcb_key_symbols_alloc(connection); + defer c.xcb_key_symbols_free(keysyms); const setup = c.xcb_get_setup(connection); const iter = c.xcb_setup_roots_iterator(setup); const screen = iter.data; const mask = c.XCB_CW_EVENT_MASK; - const value = c.XCB_EVENT_MASK_EXPOSURE; + const value = c.XCB_EVENT_MASK_EXPOSURE | c.XCB_EVENT_MASK_KEY_PRESS | c.XCB_EVENT_MASK_KEY_RELEASE; const window = c.xcb_generate_id(connection); _ = c.xcb_create_window(connection, c.XCB_COPY_FROM_PARENT, window, screen.*.root, 0, 0, 800, 600, 10, c.XCB_WINDOW_CLASS_INPUT_OUTPUT, screen.*.root_visual, mask, &value); @@ -170,6 +173,16 @@ pub fn main() !void { while (true) { if (c.xcb_poll_for_event(connection)) |e| { switch (e.*.response_type & ~@as(u32, 0x80)) { + c.XCB_KEY_PRESS => { + const ev: *c.xcb_key_press_event_t = @ptrCast(e); + const key = c.xcb_key_symbols_get_keysym(keysyms, ev.detail, 0); + sideros.sideros_key_callback(key, false); + }, + c.XCB_KEY_RELEASE => { + const ev: *c.xcb_key_release_event_t = @ptrCast(e); + const key = c.xcb_key_symbols_get_keysym(keysyms, ev.detail, 0); + sideros.sideros_key_callback(key, false); + }, else => {}, } std.c.free(e);