Implemented keyboard input

This commit is contained in:
Lorenzo Torres 2025-08-10 20:01:31 +02:00
parent 4eed1778a6
commit b6d50a781d
11 changed files with 179 additions and 59 deletions

View file

@ -77,9 +77,11 @@ pub fn build(b: *std.Build) void {
if (wayland) { if (wayland) {
exe.root_module.addIncludePath(b.path("ext")); exe.root_module.addIncludePath(b.path("ext"));
exe.linkSystemLibrary("wayland-client"); exe.linkSystemLibrary("wayland-client");
exe.linkSystemLibrary("xkbcommon");
exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") }); exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") });
} else { } else {
exe.linkSystemLibrary("xcb"); exe.linkSystemLibrary("xcb");
exe.linkSystemLibrary("xcb-keysyms");
exe.linkSystemLibrary("xcb-icccm"); exe.linkSystemLibrary("xcb-icccm");
} }
b.installArtifact(exe); b.installArtifact(exe);

View file

@ -22,32 +22,32 @@ pub const KeyCode = enum(u32) {
@"9" = 57, @"9" = 57,
semicolon = 59, semicolon = 59,
equal = 61, equal = 61,
a = 65, a = 97,
b = 66, b = 98,
c = 67, c = 99,
d = 68, d = 100,
e = 69, e = 101,
f = 70, f = 102,
g = 71, g = 103,
h = 72, h = 104,
i = 73, i = 105,
j = 74, j = 106,
k = 75, k = 107,
l = 76, l = 108,
m = 77, m = 109,
n = 78, n = 110,
o = 79, o = 111,
p = 80, p = 112,
q = 81, q = 113,
r = 82, r = 114,
s = 83, s = 115,
t = 84, t = 116,
u = 85, u = 117,
v = 86, v = 118,
w = 87, w = 119,
x = 88, x = 120,
y = 89, y = 121,
z = 90, z = 122,
left_bracket = 91, left_bracket = 91,
backslash = 92, backslash = 92,
right_bracket = 93, right_bracket = 93,
@ -124,6 +124,7 @@ pub const KeyCode = enum(u32) {
right_alt = 346, right_alt = 346,
right_super = 347, right_super = 347,
menu = 348, menu = 348,
_,
}; };
key_pressed: [@intFromEnum(KeyCode.menu)]bool = .{false} ** @intFromEnum(Input.KeyCode.menu), key_pressed: [@intFromEnum(KeyCode.menu)]bool = .{false} ** @intFromEnum(Input.KeyCode.menu),

View file

@ -13,10 +13,10 @@ pub const SystemGroup = []const System;
pub const SyncGroup = []const System; pub const SyncGroup = []const System;
pub const Resources = struct { pub const Resources = struct {
camera: Camera, camera: *Camera,
renderer: *Renderer, renderer: *Renderer,
input: Input, input: *Input,
delta_time: f64 = 0.0, delta_time: f32 = 0.0,
}; };
pub const Human = struct { pub const Human = struct {

View file

@ -47,8 +47,7 @@ pub const Matrix = extern struct {
pub fn lookAt(eye: [3]f32, target: [3]f32, arbitrary_up: [3]f32) Matrix { pub fn lookAt(eye: [3]f32, target: [3]f32, arbitrary_up: [3]f32) Matrix {
const t: @Vector(3, f32) = target; const t: @Vector(3, f32) = target;
var e: @Vector(3, f32) = eye; const e: @Vector(3, f32) = eye;
e = -e;
const u: @Vector(3, f32) = arbitrary_up; const u: @Vector(3, f32) = arbitrary_up;
const forward = normalize(t - e); const forward = normalize(t - e);
const right = normalize(cross(forward, u)); const right = normalize(cross(forward, u));

View file

@ -12,32 +12,39 @@ pub const Uniform = struct {
}; };
position: @Vector(3, f32), position: @Vector(3, f32),
target: @Vector(3, f32) = .{ 0.0, 0.0, 0.0 }, target: @Vector(3, f32) = .{ 0.0, 0.0, -1.0 },
front: @Vector(3, f32) = .{ 0.0, 0.0, 1.0 },
up: @Vector(3, f32) = .{ 0.0, 1.0, 0.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 { 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); 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 { pub fn getView(self: *Camera) math.Matrix {
return math.Matrix.lookAt(self.position, self.target, self.up); 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 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)) { if (input.isKeyDown(.w)) {
camera.position += (camera.front * (camera.speed * pool.resources.delta_time)); camera.position += camera.target * mul;
} }
if (input.isKeyDown(.s)) { if (input.isKeyDown(.s)) {
camera.position -= (camera.front * (camera.speed * pool.resources.delta_time)); camera.position -= camera.target * mul;
} }
if (input.isKeyDown(.a)) { 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)) { 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;
} }
} }

View file

@ -185,6 +185,7 @@ pub fn create_device(self: *PhysicalDevice, surface: vk.Surface, allocator: Allo
samples = 2; samples = 2;
} }
std.debug.print("Using {} samples for MSAA\n", .{samples}); std.debug.print("Using {} samples for MSAA\n", .{samples});
return .{ return .{

View file

@ -109,10 +109,12 @@ pub fn render(pool: *ecs.Pool) anyerror!void {
const now = try std.time.Instant.now(); 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); 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; renderer.previous_time = now;
const view = camera.getView();
const view_memory = renderer.graphics_pipeline.view_memory; 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_memory = renderer.graphics_pipeline.view_pos_memory;
const view_pos: [*]f32 = @alignCast(@ptrCast(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[1] = camera.position[1];
view_pos[2] = camera.position[2]; view_pos[2] = camera.position[2];
_ = delta_time;
const transform_memory = renderer.graphics_pipeline.transform_buffer.mapped_memory; const transform_memory = renderer.graphics_pipeline.transform_buffer.mapped_memory;
try renderer.device.waitFence(renderer.current_frame); try renderer.device.waitFence(renderer.current_frame);

View file

@ -14,6 +14,10 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
var pool: ecs.Pool = undefined; var pool: ecs.Pool = undefined;
var renderer: Renderer = 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; var resources: ecs.Resources = undefined;
fn init_mods() void { fn init_mods() void {
@ -47,18 +51,15 @@ fn init_mods() void {
export fn sideros_init(init: api.GameInit) callconv(.c) void { export fn sideros_init(init: api.GameInit) callconv(.c) void {
resources = .{ resources = .{
.camera = .{ .camera = &camera,
.position = .{ 0.0, 5.0, -5.0 },
.target = .{ 0.0, 0.0, 0.0 },
},
.renderer = undefined, .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"); pool = ecs.Pool.init(allocator, &resources) catch @panic("TODO: Gracefully handle error");
// TODO(ernesto): I think this @ptrCast are unavoidable but maybe not? // 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"); 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.resources.renderer = &renderer;
pool.tick(); pool.tick();
init_mods(); init_mods();
@ -74,3 +75,13 @@ export fn sideros_cleanup() callconv(.c) void {
pool.deinit(); pool.deinit();
if (gpa.deinit() != .ok) @panic("Memory leaked"); 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;
}
}
}

View file

@ -1,14 +1,16 @@
#include "vulkan/vulkan.h" #include "vulkan/vulkan.h"
#include <stdbool.h>
typedef struct { typedef struct {
VkInstance instance; VkInstance instance;
VkSurfaceKHR surface; VkSurfaceKHR surface;
} GameInit; } GameInit;
typedef struct { typedef struct {
double dt; double dt;
} GameUpdate; } GameUpdate;
void sideros_init(GameInit init); void sideros_init(GameInit init);
void sideros_update(GameUpdate state); void sideros_update(GameUpdate state);
void sideros_key_callback(unsigned int key, bool release);
void sideros_cleanup(void); void sideros_cleanup(void);

View file

@ -2,6 +2,7 @@ const std = @import("std");
const c = @cImport({ const c = @cImport({
@cInclude("wayland-client.h"); @cInclude("wayland-client.h");
@cInclude("xdg-shell.h"); @cInclude("xdg-shell.h");
@cInclude("xkbcommon/xkbcommon.h");
@cInclude("vulkan/vulkan.h"); @cInclude("vulkan/vulkan.h");
@cInclude("vulkan/vulkan_wayland.h"); @cInclude("vulkan/vulkan_wayland.h");
}); });
@ -16,11 +17,22 @@ var quit = false;
var new_width: u32 = 0; var new_width: u32 = 0;
var new_height: u32 = 0; var new_height: u32 = 0;
fn mapKeysym(keysym: u32) u32 {
return switch (keysym) {
0xffe1 => 340,
else => keysym,
};
}
const State = struct { const State = struct {
compositor: ?*c.wl_compositor = null, compositor: ?*c.wl_compositor = null,
shell: ?*c.xdg_wm_base = null, shell: ?*c.xdg_wm_base = null,
surface: ?*c.wl_surface = null, surface: ?*c.wl_surface = null,
seat: ?*c.wl_seat = null,
configured: bool = false, 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{ 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))) { } 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)); 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); _ = 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); _ = 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 = .{ const frame_listener: c.wl_callback_listener = .{
.done = frameHandleDone, .done = frameHandleDone,
}; };
@ -239,8 +309,22 @@ const registry_listener: c.wl_registry_listener = .{
.global_remove = registryHandleGlobalRemove, .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 { 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); const display = c.wl_display_connect(null);
defer c.wl_display_disconnect(display); defer c.wl_display_disconnect(display);
if (display == null) { if (display == null) {
@ -251,6 +335,9 @@ pub fn main() !void {
_ = c.wl_registry_add_listener(registry, &registry_listener, @ptrCast(&state)); _ = c.wl_registry_add_listener(registry, &registry_listener, @ptrCast(&state));
_ = c.wl_display_roundtrip(display); _ = 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 surface = c.wl_compositor_create_surface(state.compositor);
const xdg_surface = c.xdg_wm_base_get_xdg_surface(state.shell, surface); const xdg_surface = c.xdg_wm_base_get_xdg_surface(state.shell, surface);
_ = c.xdg_surface_add_listener(xdg_surface, &surface_listener, @ptrCast(&state)); _ = 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); const gameInit = try vulkan_init(allocator, display, surface);
defer vulkan_cleanup(gameInit); defer vulkan_cleanup(gameInit);

View file

@ -4,6 +4,7 @@ const sideros = @cImport({
}); });
const c = @cImport({ const c = @cImport({
@cInclude("xcb/xcb.h"); @cInclude("xcb/xcb.h");
@cInclude("xcb/xcb_keysyms.h");
@cInclude("vulkan/vulkan.h"); @cInclude("vulkan/vulkan.h");
@cInclude("vulkan/vulkan_xcb.h"); @cInclude("vulkan/vulkan_xcb.h");
@cInclude("xcb/xcb_icccm.h"); @cInclude("xcb/xcb_icccm.h");
@ -138,13 +139,15 @@ fn vulkan_cleanup(gameInit: sideros.GameInit) void {
pub fn main() !void { pub fn main() !void {
const connection = c.xcb_connect(null, null); const connection = c.xcb_connect(null, null);
defer c.xcb_disconnect(connection); 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 setup = c.xcb_get_setup(connection);
const iter = c.xcb_setup_roots_iterator(setup); const iter = c.xcb_setup_roots_iterator(setup);
const screen = iter.data; const screen = iter.data;
const mask = c.XCB_CW_EVENT_MASK; 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); 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); _ = 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) { while (true) {
if (c.xcb_poll_for_event(connection)) |e| { if (c.xcb_poll_for_event(connection)) |e| {
switch (e.*.response_type & ~@as(u32, 0x80)) { 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 => {}, else => {},
} }
std.c.free(e); std.c.free(e);