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) {
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);

View file

@ -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),

View file

@ -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 {

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 {
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));

View file

@ -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;
}
}

View file

@ -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 .{

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -1,14 +1,16 @@
#include "vulkan/vulkan.h"
#include <stdbool.h>
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);

View file

@ -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, &registry_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);

View file

@ -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);