From b1bd949db52e7d92e927c3d9bf64f229bd461433 Mon Sep 17 00:00:00 2001 From: Ernesto Lanchares Date: Tue, 5 Aug 2025 18:05:17 +0000 Subject: [PATCH] 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 --- build.zig | 96 ++++++++++++----------- src/{ => ecs}/Input.zig | 0 src/ecs/ecs.zig | 1 + src/ecs/entities.zig | 2 +- src/renderer/Camera.zig | 9 +-- src/renderer/Mesh.zig | 15 ++-- src/renderer/Renderer.zig | 13 +--- src/renderer/vulkan.zig | 125 ++---------------------------- src/sideros.zig | 43 +++++++++-- src/sideros_api.h | 13 ++++ src/wayland.zig | 159 +++++++++++++++++++++++++++++++++++--- src/xorg.zig | 156 ++++++++++++++++++++++++++++++++++--- 12 files changed, 420 insertions(+), 212 deletions(-) rename src/{ => ecs}/Input.zig (100%) create mode 100644 src/sideros_api.h diff --git a/build.zig b/build.zig index a8f35df..79b1194 100644 --- a/build.zig +++ b/build.zig @@ -6,8 +6,8 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const sideros = b.createModule(.{ - .root_source_file = b.path("src/sideros.zig"), + const math = b.createModule(.{ + .root_source_file = b.path("src/math.zig"), .target = target, .optimize = optimize, }); @@ -17,14 +17,12 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - mods.addImport("sideros", sideros); const ecs = b.createModule(.{ .root_source_file = b.path("src/ecs/ecs.zig"), .target = target, .optimize = optimize, }); - ecs.addImport("sideros", sideros); const renderer = b.createModule(.{ .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.addImport("sideros", sideros); + renderer.addImport("math", math); renderer.addImport("ecs", ecs); + // TODO(ernesto): ecs and renderer should be decoupled ecs.addImport("renderer", renderer); compileAllShaders(b, renderer); - sideros.addImport("mods", mods); - sideros.addImport("ecs", ecs); - sideros.addImport("renderer", renderer); - - const exe = b.addExecutable(.{ + const sideros = b.addStaticLibrary(.{ .name = "sideros", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }), + .root_source_file = b.path("src/sideros.zig"), + .target = target, + .optimize = optimize, }); - exe.root_module.addImport("sideros", sideros); sideros.addIncludePath(b.path("ext")); + sideros.addIncludePath(b.path("src")); - exe.linkSystemLibrary("vulkan"); - exe.linkLibC(); + sideros.root_module.addImport("mods", mods); + sideros.root_module.addImport("ecs", ecs); + sideros.root_module.addImport("renderer", renderer); + + b.installArtifact(sideros); const options = b.addOptions(); - if (target.result.os.tag == .linux) { - const wayland = b.option(bool, "wayland", "Use Wayland to create the main window") orelse false; - if (wayland) { - exe.linkSystemLibrary("wayland-client"); - exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") }); - } else { - exe.linkSystemLibrary("xcb"); - exe.linkSystemLibrary("xcb-icccm"); - } - options.addOption(bool, "wayland", wayland); + switch (target.result.os.tag) { + .linux => { + const wayland = b.option(bool, "wayland", "Use Wayland to create the main window") orelse false; + options.addOption(bool, "wayland", wayland); + + const exe = b.addExecutable(.{ + .name = if (wayland) "sideros-wayland" else "sideros-xorg", + .root_module = b.createModule(.{ + .root_source_file = b.path(if (wayland) "src/wayland.zig" else "src/xorg.zig"), + .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(.{ - .source_dir = root_lib.getEmittedDocs(), + .source_dir = sideros.getEmittedDocs(), .install_dir = .prefix, .install_subdir = "docs/sideros", }); const docs_step = b.step("docs", "Generate documentation"); docs_step.dependOn(&install_docs.step); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); + //const run_cmd = b.addRunArtifact(exe); + //run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } + //if (b.args) |args| { + //run_cmd.addArgs(args); + //} - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); + //const run_step = b.step("run", "Run the app"); + //run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = b.createModule(.{ diff --git a/src/Input.zig b/src/ecs/Input.zig similarity index 100% rename from src/Input.zig rename to src/ecs/Input.zig diff --git a/src/ecs/ecs.zig b/src/ecs/ecs.zig index 4ec765f..432ffde 100644 --- a/src/ecs/ecs.zig +++ b/src/ecs/ecs.zig @@ -1,5 +1,6 @@ pub const components = @import("components.zig"); pub const entities = @import("entities.zig"); +pub const Input = @import("Input.zig"); pub const SystemError = error{ fail, diff --git a/src/ecs/entities.zig b/src/ecs/entities.zig index 864c55e..a848108 100644 --- a/src/ecs/entities.zig +++ b/src/ecs/entities.zig @@ -4,8 +4,8 @@ const components = @import("components.zig"); const sparse = @import("sparse.zig"); const Renderer = @import("renderer"); const Camera = @import("renderer").Camera; -const Input = @import("sideros").Input; const ecs = @import("ecs.zig"); +const Input = ecs.Input; pub const System = ecs.System; pub const SystemGroup = []const System; diff --git a/src/renderer/Camera.zig b/src/renderer/Camera.zig index b4c0cef..18d06b1 100644 --- a/src/renderer/Camera.zig +++ b/src/renderer/Camera.zig @@ -1,7 +1,6 @@ const std = @import("std"); const ecs = @import("ecs"); -const sideros = @import("sideros"); -const math = sideros.math; +const math = @import("math"); const Camera = @This(); const UP = @Vector(3, f32){ 0.0, 1.0, 0.0 }; @@ -13,9 +12,9 @@ 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 }, -up: @Vector(3, f32) = .{0.0, 1.0, 0.0 }, +target: @Vector(3, f32) = .{ 0.0, 0.0, 0.0 }, +front: @Vector(3, f32) = .{ 0.0, 0.0, 1.0 }, +up: @Vector(3, f32) = .{ 0.0, 1.0, 0.0 }, speed: f32 = 2.5, pub fn getProjection(width: usize, height: usize) math.Matrix { diff --git a/src/renderer/Mesh.zig b/src/renderer/Mesh.zig index 74d6cac..3c082e1 100644 --- a/src/renderer/Mesh.zig +++ b/src/renderer/Mesh.zig @@ -1,4 +1,3 @@ -const c = @import("sideros").c; const std = @import("std"); const vk = @import("vulkan.zig"); const gltf = @import("gltf.zig"); @@ -19,11 +18,11 @@ pub const Vertex = struct { }; } - pub fn bindingDescription() c.VkVertexInputBindingDescription { - const binding_description: c.VkVertexInputBindingDescription = .{ + pub fn bindingDescription() vk.c.VkVertexInputBindingDescription { + const binding_description: vk.c.VkVertexInputBindingDescription = .{ .binding = 0, .stride = @sizeOf(Vertex), - .inputRate = c.VK_VERTEX_INPUT_RATE_VERTEX, + .inputRate = vk.c.VK_VERTEX_INPUT_RATE_VERTEX, }; 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); - try vk.mapError(c.vkMapMemory( + try vk.mapError(vk.c.vkMapMemory( device.handle, buffer.memory, 0, @@ -104,7 +103,7 @@ pub fn createVertexBuffer(allocator: Allocator, device: anytype) !vk.Buffer { @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); @@ -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); - try vk.mapError(c.vkMapMemory( + try vk.mapError(vk.c.vkMapMemory( device.handle, buffer.memory, 0, @@ -141,7 +140,7 @@ pub fn createIndexBuffer(allocator: Allocator, device: anytype) !vk.Buffer { @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); diff --git a/src/renderer/Renderer.zig b/src/renderer/Renderer.zig index 667f1c5..9d26a5c 100644 --- a/src/renderer/Renderer.zig +++ b/src/renderer/Renderer.zig @@ -1,5 +1,4 @@ -const c = @import("sideros").c; -const math = @import("sideros").math; +const math = @import("math"); const ecs = @import("ecs"); const std = @import("std"); const vk = @import("vulkan.zig"); @@ -21,11 +20,9 @@ current_frame: u32, vertex_buffer: vk.Buffer, index_buffer: vk.Buffer, -pub fn init(comptime C: type, comptime S: type, allocator: Allocator, display: C, s: S) !Renderer { - const instance = try vk.Instance.create(allocator); - - const surface = try vk.Surface.create(C, S, instance, display, s); - +pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_handle: vk.c.VkSurfaceKHR) !Renderer { + const instance: vk.Instance = .{ .handle = instance_handle }; + const surface: vk.Surface = .{ .handle = surface_handle }; var physical_device = try vk.PhysicalDevice.pick(allocator, instance); 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.render_pass.destroy(self.device); 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? diff --git a/src/renderer/vulkan.zig b/src/renderer/vulkan.zig index 07f5edf..8bcf90f 100644 --- a/src/renderer/vulkan.zig +++ b/src/renderer/vulkan.zig @@ -1,14 +1,14 @@ pub const Texture = @import("Texture.zig"); 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 sideros = @import("sideros"); const Camera = @import("Camera.zig"); -const math = sideros.math; const Allocator = std.mem.Allocator; -const config = sideros.config; const builtin = @import("builtin"); const debug = (builtin.mode == .Debug); @@ -73,92 +73,6 @@ pub const BufferFlags = packed struct(u32) { pub const Instance = struct { 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 { @@ -608,7 +522,6 @@ pub fn GraphicsPipeline(comptime n: usize) type { @ptrCast(&view_data), )); - const view_descriptor_buffer_info = c.VkDescriptorBufferInfo{ .buffer = view_buffer.handle, .offset = 0, @@ -862,7 +775,7 @@ pub fn Swapchain(comptime n: usize) type { if (capabilities.currentExtent.width != std.math.maxInt(u32)) { extent = capabilities.currentExtent; } else { - const width: u32, const height: u32 = .{800, 600}; + const width: u32, const height: u32 = .{ 800, 600 }; extent = .{ .width = @intCast(width), @@ -993,30 +906,6 @@ pub fn Swapchain(comptime n: usize) type { pub const Surface = struct { 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 { var mode_count: u32 = 0; 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)); return caps; } - - pub fn destroy(self: Surface, instance: Instance) void { - c.vkDestroySurfaceKHR(instance.handle, self.handle, null); - } }; pub fn Device(comptime n: usize) type { diff --git a/src/sideros.zig b/src/sideros.zig index 19f032f..ed4ba5a 100644 --- a/src/sideros.zig +++ b/src/sideros.zig @@ -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 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"); +} diff --git a/src/sideros_api.h b/src/sideros_api.h new file mode 100644 index 0000000..7319e07 --- /dev/null +++ b/src/sideros_api.h @@ -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); diff --git a/src/wayland.zig b/src/wayland.zig index 8a3ef00..b75eaba 100644 --- a/src/wayland.zig +++ b/src/wayland.zig @@ -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)); diff --git a/src/xorg.zig b/src/xorg.zig index df5b4fa..6b9b4ba 100644 --- a/src/xorg.zig +++ b/src/xorg.zig @@ -1,9 +1,141 @@ const std = @import("std"); -const Renderer = @import("sideros").Renderer; -const c = @import("sideros").c; -const ecs = @import("sideros").ecs; +const sideros = @cImport({ + @cInclude("sideros_api.h"); +}); +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); defer c.xcb_disconnect(connection); @@ -26,10 +158,14 @@ pub fn init(allocator: std.mem.Allocator, pool: *ecs.Pool) !void { _ = c.xcb_flush(connection); - var renderer = try Renderer.init(@TypeOf(connection), @TypeOf(window), allocator, connection, window); - defer renderer.deinit(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + 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) { 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); } - - pool.tick(); + const gameUpdate: sideros.GameUpdate = undefined; + sideros.sideros_update(gameUpdate); } + + sideros.sideros_cleanup(); }