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

@ -6,8 +6,8 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const sideros = b.createModule(.{ const math = b.createModule(.{
.root_source_file = b.path("src/sideros.zig"), .root_source_file = b.path("src/math.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@ -17,14 +17,12 @@ pub fn build(b: *std.Build) void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
mods.addImport("sideros", sideros);
const ecs = b.createModule(.{ const ecs = b.createModule(.{
.root_source_file = b.path("src/ecs/ecs.zig"), .root_source_file = b.path("src/ecs/ecs.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
ecs.addImport("sideros", sideros);
const renderer = b.createModule(.{ const renderer = b.createModule(.{
.root_source_file = b.path("src/renderer/Renderer.zig"), .root_source_file = b.path("src/renderer/Renderer.zig"),
@ -34,69 +32,79 @@ pub fn build(b: *std.Build) void {
}); });
renderer.addCSourceFile(.{ .file = b.path("ext/stb_image.c") }); renderer.addCSourceFile(.{ .file = b.path("ext/stb_image.c") });
renderer.addImport("sideros", sideros); renderer.addImport("sideros", sideros);
renderer.addImport("math", math);
renderer.addImport("ecs", ecs); renderer.addImport("ecs", ecs);
// TODO(ernesto): ecs and renderer should be decoupled
ecs.addImport("renderer", renderer); ecs.addImport("renderer", renderer);
compileAllShaders(b, renderer); compileAllShaders(b, renderer);
sideros.addImport("mods", mods); const sideros = b.addStaticLibrary(.{
sideros.addImport("ecs", ecs);
sideros.addImport("renderer", renderer);
const exe = b.addExecutable(.{
.name = "sideros", .name = "sideros",
.root_module = b.createModule(.{ .root_source_file = b.path("src/sideros.zig"),
.root_source_file = b.path("src/main.zig"), .target = target,
.target = target, .optimize = optimize,
.optimize = optimize,
}),
}); });
exe.root_module.addImport("sideros", sideros);
sideros.addIncludePath(b.path("ext")); sideros.addIncludePath(b.path("ext"));
sideros.addIncludePath(b.path("src"));
exe.linkSystemLibrary("vulkan"); sideros.root_module.addImport("mods", mods);
exe.linkLibC(); sideros.root_module.addImport("ecs", ecs);
sideros.root_module.addImport("renderer", renderer);
b.installArtifact(sideros);
const options = b.addOptions(); const options = b.addOptions();
if (target.result.os.tag == .linux) { switch (target.result.os.tag) {
const wayland = b.option(bool, "wayland", "Use Wayland to create the main window") orelse false; .linux => {
if (wayland) { const wayland = b.option(bool, "wayland", "Use Wayland to create the main window") orelse false;
exe.linkSystemLibrary("wayland-client"); options.addOption(bool, "wayland", wayland);
exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") });
} else { const exe = b.addExecutable(.{
exe.linkSystemLibrary("xcb"); .name = if (wayland) "sideros-wayland" else "sideros-xorg",
exe.linkSystemLibrary("xcb-icccm"); .root_module = b.createModule(.{
} .root_source_file = b.path(if (wayland) "src/wayland.zig" else "src/xorg.zig"),
options.addOption(bool, "wayland", wayland); .target = target,
.optimize = optimize,
}),
});
exe.root_module.addIncludePath(b.path("src"));
exe.linkLibrary(sideros);
exe.linkLibC();
exe.linkSystemLibrary("vulkan");
if (wayland) {
exe.root_module.addIncludePath(b.path("ext"));
exe.linkSystemLibrary("wayland-client");
exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") });
} else {
exe.linkSystemLibrary("xcb");
exe.linkSystemLibrary("xcb-icccm");
}
b.installArtifact(exe);
},
else => {
std.debug.panic("Compilation not implemented for OS: {any}\n", .{target.result.os.tag});
},
} }
sideros.addOptions("config", options);
b.installArtifact(exe);
const root_lib = b.addLibrary(.{
.root_module = sideros,
.name = "sideros",
});
const install_docs = b.addInstallDirectory(.{ const install_docs = b.addInstallDirectory(.{
.source_dir = root_lib.getEmittedDocs(), .source_dir = sideros.getEmittedDocs(),
.install_dir = .prefix, .install_dir = .prefix,
.install_subdir = "docs/sideros", .install_subdir = "docs/sideros",
}); });
const docs_step = b.step("docs", "Generate documentation"); const docs_step = b.step("docs", "Generate documentation");
docs_step.dependOn(&install_docs.step); docs_step.dependOn(&install_docs.step);
const run_cmd = b.addRunArtifact(exe); //const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep()); //run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| { //if (b.args) |args| {
run_cmd.addArgs(args); //run_cmd.addArgs(args);
} //}
const run_step = b.step("run", "Run the app"); //const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step); //run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{ const exe_unit_tests = b.addTest(.{
.root_module = b.createModule(.{ .root_module = b.createModule(.{

View file

@ -1,5 +1,6 @@
pub const components = @import("components.zig"); pub const components = @import("components.zig");
pub const entities = @import("entities.zig"); pub const entities = @import("entities.zig");
pub const Input = @import("Input.zig");
pub const SystemError = error{ pub const SystemError = error{
fail, fail,

View file

@ -4,8 +4,8 @@ const components = @import("components.zig");
const sparse = @import("sparse.zig"); const sparse = @import("sparse.zig");
const Renderer = @import("renderer"); const Renderer = @import("renderer");
const Camera = @import("renderer").Camera; const Camera = @import("renderer").Camera;
const Input = @import("sideros").Input;
const ecs = @import("ecs.zig"); const ecs = @import("ecs.zig");
const Input = ecs.Input;
pub const System = ecs.System; pub const System = ecs.System;
pub const SystemGroup = []const System; pub const SystemGroup = []const System;

View file

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const ecs = @import("ecs"); const ecs = @import("ecs");
const sideros = @import("sideros"); const math = @import("math");
const math = sideros.math;
const Camera = @This(); const Camera = @This();
const UP = @Vector(3, f32){ 0.0, 1.0, 0.0 }; const UP = @Vector(3, f32){ 0.0, 1.0, 0.0 };
@ -13,9 +12,9 @@ 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, 0.0 },
front: @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 = 2.5,
pub fn getProjection(width: usize, height: usize) math.Matrix { pub fn getProjection(width: usize, height: usize) math.Matrix {

View file

@ -1,4 +1,3 @@
const c = @import("sideros").c;
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan.zig"); const vk = @import("vulkan.zig");
const gltf = @import("gltf.zig"); const gltf = @import("gltf.zig");
@ -19,11 +18,11 @@ pub const Vertex = struct {
}; };
} }
pub fn bindingDescription() c.VkVertexInputBindingDescription { pub fn bindingDescription() vk.c.VkVertexInputBindingDescription {
const binding_description: c.VkVertexInputBindingDescription = .{ const binding_description: vk.c.VkVertexInputBindingDescription = .{
.binding = 0, .binding = 0,
.stride = @sizeOf(Vertex), .stride = @sizeOf(Vertex),
.inputRate = c.VK_VERTEX_INPUT_RATE_VERTEX, .inputRate = vk.c.VK_VERTEX_INPUT_RATE_VERTEX,
}; };
return binding_description; return binding_description;
@ -89,7 +88,7 @@ pub fn createVertexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf([8]f32) * vertices.len); const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf([8]f32) * vertices.len);
try vk.mapError(c.vkMapMemory( try vk.mapError(vk.c.vkMapMemory(
device.handle, device.handle,
buffer.memory, buffer.memory,
0, 0,
@ -104,7 +103,7 @@ pub fn createVertexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
@memcpy(gpu_vertices, @as([]Vertex, @ptrCast(final_array[0..]))); @memcpy(gpu_vertices, @as([]Vertex, @ptrCast(final_array[0..])));
} }
c.vkUnmapMemory(device.handle, buffer.memory); vk.c.vkUnmapMemory(device.handle, buffer.memory);
const vertex_buffer = try device.createBuffer(vk.BufferUsage{ .vertex_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(Vertex) * vertices.len); const vertex_buffer = try device.createBuffer(vk.BufferUsage{ .vertex_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(Vertex) * vertices.len);
@ -126,7 +125,7 @@ pub fn createIndexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf(u16) * indices.len); const buffer = try device.createBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf(u16) * indices.len);
try vk.mapError(c.vkMapMemory( try vk.mapError(vk.c.vkMapMemory(
device.handle, device.handle,
buffer.memory, buffer.memory,
0, 0,
@ -141,7 +140,7 @@ pub fn createIndexBuffer(allocator: Allocator, device: anytype) !vk.Buffer {
@memcpy(gpu_indices, indices[0..]); @memcpy(gpu_indices, indices[0..]);
} }
c.vkUnmapMemory(device.handle, buffer.memory); vk.c.vkUnmapMemory(device.handle, buffer.memory);
const index_buffer = try device.createBuffer(vk.BufferUsage{ .index_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(u16) * indices.len); const index_buffer = try device.createBuffer(vk.BufferUsage{ .index_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(u16) * indices.len);

View file

@ -1,5 +1,4 @@
const c = @import("sideros").c; const math = @import("math");
const math = @import("sideros").math;
const ecs = @import("ecs"); const ecs = @import("ecs");
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan.zig"); const vk = @import("vulkan.zig");
@ -21,11 +20,9 @@ current_frame: u32,
vertex_buffer: vk.Buffer, vertex_buffer: vk.Buffer,
index_buffer: vk.Buffer, index_buffer: vk.Buffer,
pub fn init(comptime C: type, comptime S: type, allocator: Allocator, display: C, s: S) !Renderer { pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_handle: vk.c.VkSurfaceKHR) !Renderer {
const instance = try vk.Instance.create(allocator); const instance: vk.Instance = .{ .handle = instance_handle };
const surface: vk.Surface = .{ .handle = surface_handle };
const surface = try vk.Surface.create(C, S, instance, display, s);
var physical_device = try vk.PhysicalDevice.pick(allocator, instance); var physical_device = try vk.PhysicalDevice.pick(allocator, instance);
const device = try physical_device.create_device(surface, allocator, 2); const device = try physical_device.create_device(surface, allocator, 2);
@ -81,8 +78,6 @@ pub fn deinit(self: Renderer) void {
self.swapchain.destroy(self.device); self.swapchain.destroy(self.device);
self.render_pass.destroy(self.device); self.render_pass.destroy(self.device);
self.device.destroy(); self.device.destroy();
self.surface.destroy(self.instance);
self.instance.destroy();
} }
// TODO: render is maybe a bad name? something like present() or submit() is better? // TODO: render is maybe a bad name? something like present() or submit() is better?

View file

@ -1,14 +1,14 @@
pub const Texture = @import("Texture.zig"); pub const Texture = @import("Texture.zig");
const std = @import("std"); const std = @import("std");
const c = @import("sideros").c; pub const c = @cImport({
@cInclude("vulkan/vulkan.h");
});
const math = @import("math");
const Mesh = @import("Mesh.zig"); const Mesh = @import("Mesh.zig");
const sideros = @import("sideros");
const Camera = @import("Camera.zig"); const Camera = @import("Camera.zig");
const math = sideros.math;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const config = sideros.config;
const builtin = @import("builtin"); const builtin = @import("builtin");
const debug = (builtin.mode == .Debug); const debug = (builtin.mode == .Debug);
@ -73,92 +73,6 @@ pub const BufferFlags = packed struct(u32) {
pub const Instance = struct { pub const Instance = struct {
handle: c.VkInstance, handle: c.VkInstance,
pub fn create(allocator: Allocator) !Instance {
const extensions = [_][*c]const u8 {if (config.wayland) c.VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME else c.VK_KHR_XCB_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
var avaliableExtensions = std.ArrayList(c.VkExtensionProperties).init(allocator);
try avaliableExtensions.resize(avaliableExtensionsCount);
defer avaliableExtensions.deinit();
_ = c.vkEnumerateInstanceExtensionProperties(null, &avaliableExtensionsCount, avaliableExtensions.items.ptr);
// Check the extensions we want against the extensions the user has
for (extensions) |need_ext| {
var found = false;
for (avaliableExtensions.items) |useable_ext| {
const extensionName: [*c]const u8 = &useable_ext.extensionName;
if (std.mem.eql(u8, std.mem.sliceTo(need_ext, 0), std.mem.sliceTo(extensionName, 0))) {
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
var availableLayers = std.ArrayList(c.VkLayerProperties).init(allocator);
try availableLayers.resize(avaliableLayersCount);
defer availableLayers.deinit();
_ = c.vkEnumerateInstanceLayerProperties(&avaliableLayersCount, availableLayers.items.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.items) |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,
};
var instance: c.VkInstance = undefined;
try mapError(c.vkCreateInstance(&instance_info, null, &instance));
return Instance{
.handle = instance,
};
}
pub fn destroy(self: Instance) void {
c.vkDestroyInstance(self.handle, null);
}
}; };
pub const Buffer = struct { pub const Buffer = struct {
@ -608,7 +522,6 @@ pub fn GraphicsPipeline(comptime n: usize) type {
@ptrCast(&view_data), @ptrCast(&view_data),
)); ));
const view_descriptor_buffer_info = c.VkDescriptorBufferInfo{ const view_descriptor_buffer_info = c.VkDescriptorBufferInfo{
.buffer = view_buffer.handle, .buffer = view_buffer.handle,
.offset = 0, .offset = 0,
@ -862,7 +775,7 @@ pub fn Swapchain(comptime n: usize) type {
if (capabilities.currentExtent.width != std.math.maxInt(u32)) { if (capabilities.currentExtent.width != std.math.maxInt(u32)) {
extent = capabilities.currentExtent; extent = capabilities.currentExtent;
} else { } else {
const width: u32, const height: u32 = .{800, 600}; const width: u32, const height: u32 = .{ 800, 600 };
extent = .{ extent = .{
.width = @intCast(width), .width = @intCast(width),
@ -993,30 +906,6 @@ pub fn Swapchain(comptime n: usize) type {
pub const Surface = struct { pub const Surface = struct {
handle: c.VkSurfaceKHR, handle: c.VkSurfaceKHR,
pub fn create(comptime C: type, comptime S: type, instance: Instance, display: C, surface: S) !Surface {
var handle: c.VkSurfaceKHR = undefined;
if (config.wayland) {
const create_info: c.VkWaylandSurfaceCreateInfoKHR = .{
.sType = c.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
.display = display,
.surface = surface,
};
try mapError(c.vkCreateWaylandSurfaceKHR(instance.handle, &create_info, null, &handle));
} else {
const create_info: c.VkXcbSurfaceCreateInfoKHR = .{
.sType = c.VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
.connection = display,
.window = surface,
};
try mapError(c.vkCreateXcbSurfaceKHR(instance.handle, &create_info, null, &handle));
}
return .{
.handle = handle,
};
}
pub fn presentModes(self: Surface, allocator: Allocator, device: PhysicalDevice) ![]c.VkPresentModeKHR { pub fn presentModes(self: Surface, allocator: Allocator, device: PhysicalDevice) ![]c.VkPresentModeKHR {
var mode_count: u32 = 0; var mode_count: u32 = 0;
try mapError(c.vkGetPhysicalDeviceSurfacePresentModesKHR(device.handle, self.handle, &mode_count, null)); try mapError(c.vkGetPhysicalDeviceSurfacePresentModesKHR(device.handle, self.handle, &mode_count, null));
@ -1040,10 +929,6 @@ pub const Surface = struct {
try mapError(c.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.handle, self.handle, &caps)); try mapError(c.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.handle, self.handle, &caps));
return caps; return caps;
} }
pub fn destroy(self: Surface, instance: Instance) void {
c.vkDestroySurfaceKHR(instance.handle, self.handle, null);
}
}; };
pub fn Device(comptime n: usize) type { pub fn Device(comptime n: usize) type {

View file

@ -1,7 +1,40 @@
pub const math = @import("math.zig");
pub const Input = @import("Input.zig");
pub const mods = @import("mods");
pub const ecs = @import("ecs"); pub const ecs = @import("ecs");
pub const Renderer = @import("renderer"); pub const Renderer = @import("renderer");
pub const config = @import("config");
pub const c = @import("c.zig").c; const api = @cImport({
@cInclude("sideros_api.h");
});
const std = @import("std");
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var pool: ecs.Pool = undefined;
var renderer: Renderer = undefined;
export fn sideros_init(init: api.GameInit) callconv(.C) void {
pool = ecs.Pool.init(allocator, .{
.camera = .{
.position = .{ 0.0, 0.0, 1.0 },
.target = .{ 0.0, 0.0, 0.0 },
},
.renderer = undefined,
.input = .{ .key_pressed = .{false} ** @intFromEnum(ecs.Input.KeyCode.menu) },
}) 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.resources.renderer = renderer;
pool.tick();
}
export fn sideros_update(gameUpdate: api.GameUpdate) callconv(.C) void {
_ = gameUpdate;
pool.tick();
}
export fn sideros_cleanup() callconv(.C) void {
renderer.deinit();
pool.deinit();
if (gpa.deinit() != .ok) @panic("Memory leaked");
}

13
src/sideros_api.h Normal file
View file

@ -0,0 +1,13 @@
#include "vulkan/vulkan.h"
typedef struct {
VkInstance instance;
VkSurfaceKHR surface;
} GameInit;
typedef struct {
} GameUpdate;
void sideros_init(GameInit init);
void sideros_update(GameUpdate state);
void sideros_cleanup(void);

View file

@ -1,7 +1,15 @@
const c = @import("sideros").c;
const std = @import("std"); const std = @import("std");
const Renderer = @import("sideros").Renderer; const c = @cImport({
const ecs = @import("sideros").ecs; @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 resize = false;
var quit = false; var quit = false;
@ -12,10 +20,134 @@ 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,
pool: *ecs.Pool = undefined,
configured: bool = false, 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 { fn registryHandleGlobal(data: ?*anyopaque, registry: ?*c.wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void {
_ = version; _ = version;
const state: *State = @alignCast(@ptrCast(data)); 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); const cb = c.wl_surface_frame(state.surface);
_ = c.wl_callback_add_listener(cb, &frame_listener, state); _ = 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); _ = c.wl_surface_commit(state.surface);
} }
@ -106,7 +240,7 @@ const registry_listener: c.wl_registry_listener = .{
.global_remove = registryHandleGlobalRemove, .global_remove = registryHandleGlobalRemove,
}; };
pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void { pub fn main() !void {
var state: State = .{}; var state: State = .{};
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);
@ -138,11 +272,14 @@ pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void {
_ = c.wl_display_dispatch(display); _ = c.wl_display_dispatch(display);
} }
var renderer = try Renderer.init(@TypeOf(display), @TypeOf(surface), allocator, display, surface);
pool.resources.renderer = &renderer; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
state.pool = pool; const allocator = gpa.allocator();
pool.tick(); 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); const cb = c.wl_surface_frame(surface);
_ = c.wl_callback_add_listener(cb, &frame_listener, @ptrCast(&state)); _ = c.wl_callback_add_listener(cb, &frame_listener, @ptrCast(&state));

View file

@ -1,9 +1,141 @@
const std = @import("std"); const std = @import("std");
const Renderer = @import("sideros").Renderer; const sideros = @cImport({
const c = @import("sideros").c; @cInclude("sideros_api.h");
const ecs = @import("sideros").ecs; });
const c = @cImport({
@cInclude("xcb/xcb.h");
@cInclude("vulkan/vulkan.h");
@cInclude("vulkan/vulkan_xcb.h");
@cInclude("xcb/xcb_icccm.h");
});
pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void { const builtin = @import("builtin");
const debug = (builtin.mode == .Debug);
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_XCB_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, connection: ?*c.xcb_connection_t, window: u32, handle: *c.VkSurfaceKHR) !void {
const create_info: c.VkXcbSurfaceCreateInfoKHR = .{
.sType = c.VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
.connection = connection,
.window = window,
};
try mapError(c.vkCreateXcbSurfaceKHR(instance, &create_info, null, handle));
}
fn vulkan_init(allocator: std.mem.Allocator, connection: ?*c.xcb_connection_t, window: u32) !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), connection, window, &gameInit.surface);
return gameInit;
}
fn vulkan_cleanup(gameInit: sideros.GameInit) void {
c.vkDestroySurfaceKHR(gameInit.instance, gameInit.surface, null);
c.vkDestroyInstance(gameInit.instance, null);
}
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);
@ -26,10 +158,14 @@ pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void {
_ = c.xcb_flush(connection); _ = c.xcb_flush(connection);
var renderer = try Renderer.init(@TypeOf(connection), @TypeOf(window), allocator, connection, window); var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer renderer.deinit(); const allocator = gpa.allocator();
defer if (gpa.deinit() != .ok) @panic("Memory leaked");
pool.resources.renderer = &renderer; const gameInit = try vulkan_init(allocator, connection, window);
defer vulkan_cleanup(gameInit);
sideros.sideros_init(gameInit);
while (true) { while (true) {
if (c.xcb_poll_for_event(connection)) |e| { if (c.xcb_poll_for_event(connection)) |e| {
@ -38,7 +174,9 @@ pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void {
} }
std.c.free(e); std.c.free(e);
} }
const gameUpdate: sideros.GameUpdate = undefined;
pool.tick(); sideros.sideros_update(gameUpdate);
} }
sideros.sideros_cleanup();
} }