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"); }); const sideros = @cImport({ @cInclude("sideros_api.h"); }); const builtin = @import("builtin"); const debug = builtin.mode == .Debug; var resize = false; 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{ "VK_LAYER_KHRONOS_validation", }; const device_extensions: []const [*c]const u8 = &[_][*c]const u8{ c.VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; const Error = error{ initialization_failed, unknown_error, }; fn mapError(result: c_int) !void { return switch (result) { c.VK_SUCCESS => {}, c.VK_ERROR_INITIALIZATION_FAILED => Error.initialization_failed, else => Error.unknown_error, }; } fn vulkan_init_instance(allocator: std.mem.Allocator, handle: *c.VkInstance) !void { const extensions = [_][*c]const u8{ c.VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, c.VK_KHR_SURFACE_EXTENSION_NAME }; // Querry avaliable extensions size var avaliableExtensionsCount: u32 = 0; _ = c.vkEnumerateInstanceExtensionProperties(null, &avaliableExtensionsCount, null); // Actually querry avaliable extensions const avaliableExtensions = try allocator.alloc(c.VkExtensionProperties, avaliableExtensionsCount); defer allocator.free(avaliableExtensions); _ = c.vkEnumerateInstanceExtensionProperties(null, &avaliableExtensionsCount, avaliableExtensions.ptr); // Check the extensions we want against the extensions the user has for (extensions) |need_ext| { var found = false; const needName = std.mem.sliceTo(need_ext, 0); for (avaliableExtensions) |useable_ext| { const extensionName = useable_ext.extensionName[0..std.mem.indexOf(u8, &useable_ext.extensionName, &[_]u8{0}).?]; if (std.mem.eql(u8, needName, extensionName)) { found = true; break; } } if (!found) { std.debug.panic("ERROR: Needed vulkan extension {s} not found\n", .{need_ext}); } } // Querry avaliable layers size var avaliableLayersCount: u32 = 0; _ = c.vkEnumerateInstanceLayerProperties(&avaliableLayersCount, null); // Actually querry avaliable layers const availableLayers = try allocator.alloc(c.VkLayerProperties, avaliableLayersCount); defer allocator.free(availableLayers); _ = c.vkEnumerateInstanceLayerProperties(&avaliableLayersCount, availableLayers.ptr); // Every layer we do have we add to this list, if we don't have it no worries just print a message and continue var newLayers = std.ArrayList([*c]const u8).init(allocator); defer newLayers.deinit(); // Loop over layers we want for (validation_layers) |want_layer| { var found = false; for (availableLayers) |useable_validation| { const layer_name: [*c]const u8 = &useable_validation.layerName; if (std.mem.eql(u8, std.mem.sliceTo(want_layer, 0), std.mem.sliceTo(layer_name, 0))) { found = true; break; } } if (!found) { std.debug.print("WARNING: Compiled in debug mode, but wanted validation layer {s} not found.\n", .{want_layer}); std.debug.print("NOTE: Validation layer will be removed from the wanted validation layers\n", .{}); } else { try newLayers.append(want_layer); } } const app_info: c.VkApplicationInfo = .{ .sType = c.VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "sideros", .applicationVersion = c.VK_MAKE_VERSION(1, 0, 0), .engineVersion = c.VK_MAKE_VERSION(1, 0, 0), .pEngineName = "sideros", .apiVersion = c.VK_MAKE_VERSION(1, 3, 0), }; const instance_info: c.VkInstanceCreateInfo = .{ .sType = c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledExtensionCount = @intCast(extensions.len), .ppEnabledExtensionNames = @ptrCast(extensions[0..]), .enabledLayerCount = @intCast(newLayers.items.len), .ppEnabledLayerNames = newLayers.items.ptr, }; try mapError(c.vkCreateInstance(&instance_info, null, handle)); } fn vulkan_init_surface(instance: c.VkInstance, display: ?*c.wl_display, surface: ?*c.wl_surface, handle: *c.VkSurfaceKHR) !void { const create_info: c.VkWaylandSurfaceCreateInfoKHR = .{ .sType = c.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, .display = display, .surface = surface, }; try mapError(c.vkCreateWaylandSurfaceKHR(instance, &create_info, null, handle)); } fn vulkan_init(allocator: std.mem.Allocator, display: ?*c.wl_display, surface: ?*c.wl_surface) !sideros.GameInit { var gameInit: sideros.GameInit = undefined; try vulkan_init_instance(allocator, &gameInit.instance); // TODO(ernesto): This pointer cast is weird as fuck try vulkan_init_surface(@ptrCast(gameInit.instance), display, surface, &gameInit.surface); return gameInit; } fn vulkan_cleanup(gameInit: sideros.GameInit) void { // TODO(ernesto): again this ptr // this can be solved merging the cImports. But that seems uglier to me... c.vkDestroySurfaceKHR(@ptrCast(gameInit.instance), @ptrCast(gameInit.surface), null); c.vkDestroyInstance(@ptrCast(gameInit.instance), null); } fn registryHandleGlobal(data: ?*anyopaque, registry: ?*c.wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void { _ = version; const state: *State = @alignCast(@ptrCast(data)); if (std.mem.eql(u8, std.mem.span(interface), std.mem.span(c.wl_compositor_interface.name))) { state.compositor = @ptrCast(c.wl_registry_bind(registry.?, name, &c.wl_compositor_interface, 4)); } 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)); } } fn registryHandleGlobalRemove(data: ?*anyopaque, registry: ?*c.wl_registry, name: u32) callconv(.c) void { _ = data; _ = registry; _ = name; } fn shellHandlePing(data: ?*anyopaque, shell: ?*c.xdg_wm_base, serial: u32) callconv(.c) void { _ = data; c.xdg_wm_base_pong(shell, serial); } fn shellHandleSurfaceConfigure(data: ?*anyopaque, surface: ?*c.xdg_surface, serial: u32) callconv(.c) void { const state: *State = @alignCast(@ptrCast(data)); c.xdg_surface_ack_configure(surface, serial); state.configured = true; } fn toplevelHandleConfigure(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, width: i32, height: i32, states: ?*c.wl_array) callconv(.c) void { _ = data; _ = toplevel; _ = states; if (width != 0 and height != 0) { resize = true; new_width = @intCast(width); new_height = @intCast(height); } } fn toplevelHandleClose(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel) callconv(.c) void { _ = data; _ = toplevel; quit = true; } fn toplevelHandleConfigureBounds(data: ?*anyopaque, toplevel: ?*c.xdg_toplevel, width: i32, height: i32) callconv(.c) void { _ = data; _ = toplevel; _ = width; _ = height; } fn frameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, time: u32) callconv(.c) void { _ = time; const state: *State = @alignCast(@ptrCast(data)); _ = c.wl_callback_destroy(callback); const cb = c.wl_surface_frame(state.surface); _ = c.wl_callback_add_listener(cb, &frame_listener, state); const gameUpdate: sideros.GameUpdate = undefined; sideros.sideros_update(gameUpdate); _ = 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, }; const shell_listener: c.xdg_wm_base_listener = .{ .ping = shellHandlePing, }; const surface_listener: c.xdg_surface_listener = .{ .configure = shellHandleSurfaceConfigure, }; const toplevel_listener: c.xdg_toplevel_listener = .{ .configure = toplevelHandleConfigure, .configure_bounds = toplevelHandleConfigureBounds, .close = toplevelHandleClose, }; const registry_listener: c.wl_registry_listener = .{ .global = registryHandleGlobal, .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 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) { return error.ConnectionFailed; } const registry = c.wl_display_get_registry(display); _ = 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)); state.surface = surface; const toplevel = c.xdg_surface_get_toplevel(xdg_surface); _ = c.xdg_toplevel_add_listener(toplevel, &toplevel_listener, @ptrCast(&state)); const title = [_]u8 {'s', 'i', 'd', 'e', 'r', 'o', 's', 0}; c.xdg_toplevel_set_title(toplevel, @ptrCast(&title[0])); c.xdg_toplevel_set_app_id(toplevel, @ptrCast(&title[0])); c.xdg_toplevel_set_min_size(toplevel, 800, 600); c.xdg_toplevel_set_max_size(toplevel, 800, 600); _ = c.wl_surface_commit(surface); while (!state.configured) { _ = c.wl_display_dispatch(display); } const gameInit = try vulkan_init(allocator, display, surface); defer vulkan_cleanup(gameInit); sideros.sideros_init(gameInit); const cb = c.wl_surface_frame(surface); _ = c.wl_callback_add_listener(cb, &frame_listener, @ptrCast(&state)); _ = c.wl_surface_commit(surface); while (!quit) { _ = c.wl_display_dispatch(display); } }