Refactored compilation and startup. Now everything is orchestrated through a simple API described in sideros_api.h. Also refactored some of the code to get rid of global C imports.

Signed-off-by: Lorenzo Torres <torres@sideros.org>
This commit is contained in:
Ernesto Lanchares 2025-08-05 18:05:17 +00:00 committed by Lorenzo Torres
parent 5b51a3d571
commit b1bd949db5
12 changed files with 420 additions and 212 deletions

View file

@ -1,7 +1,15 @@
const c = @import("sideros").c;
const std = @import("std");
const Renderer = @import("sideros").Renderer;
const ecs = @import("sideros").ecs;
const c = @cImport({
@cInclude("wayland-client.h");
@cInclude("xdg-shell.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;
@ -12,10 +20,134 @@ const State = struct {
compositor: ?*c.wl_compositor = null,
shell: ?*c.xdg_wm_base = null,
surface: ?*c.wl_surface = null,
pool: *ecs.Pool = undefined,
configured: bool = false,
};
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));
@ -79,7 +211,9 @@ fn frameHandleDone(data: ?*anyopaque, callback: ?*c.wl_callback, time: u32) call
const cb = c.wl_surface_frame(state.surface);
_ = c.wl_callback_add_listener(cb, &frame_listener, state);
state.pool.tick();
const gameUpdate: sideros.GameUpdate = undefined;
sideros.sideros_update(gameUpdate);
_ = c.wl_surface_commit(state.surface);
}
@ -106,7 +240,7 @@ const registry_listener: c.wl_registry_listener = .{
.global_remove = registryHandleGlobalRemove,
};
pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void {
pub fn main() !void {
var state: State = .{};
const display = c.wl_display_connect(null);
defer c.wl_display_disconnect(display);
@ -131,18 +265,21 @@ pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void {
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);
}
var renderer = try Renderer.init(@TypeOf(display), @TypeOf(surface), allocator, display, surface);
pool.resources.renderer = &renderer;
state.pool = pool;
pool.tick();
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);
sideros.sideros_init(gameInit);
const cb = c.wl_surface_frame(surface);
_ = c.wl_callback_add_listener(cb, &frame_listener, @ptrCast(&state));