From f894fb317d76c8b04a72357a80dc05031cbf57ae Mon Sep 17 00:00:00 2001 From: Lorenzo Torres Date: Sun, 3 Aug 2025 23:23:03 +0200 Subject: [PATCH] Implemented wayland initialization code --- README.md | 1 - assets/shaders/shader.vert | 3 +- build.zig | 56 +------------- src/c.zig | 4 + src/main.zig | 94 +++++++++++------------ src/renderer/Renderer.zig | 13 ++-- src/renderer/Window.zig | 33 ++------ src/renderer/c.zig | 4 +- src/renderer/vulkan.zig | 39 ++++++---- src/wayland.zig | 152 +++++++++++++++++++++++++++++++++++++ 10 files changed, 244 insertions(+), 155 deletions(-) delete mode 100644 README.md create mode 100644 src/c.zig create mode 100644 src/wayland.zig diff --git a/README.md b/README.md deleted file mode 100644 index bbba3b1..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# Sideros diff --git a/assets/shaders/shader.vert b/assets/shaders/shader.vert index c759755..368d2e2 100644 --- a/assets/shaders/shader.vert +++ b/assets/shaders/shader.vert @@ -7,5 +7,6 @@ layout (binding = 0) uniform Uniform { } ubo; void main() { - gl_Position = ubo.proj * vec4(vertPos, 1.0); + vec4 out_vec = ubo.proj * vec4(vertPos, 1.0); + gl_Position = vec4(out_vec.x, out_vec.y, 0.5, out_vec.w); } diff --git a/build.zig b/build.zig index 378b6b5..18f935f 100644 --- a/build.zig +++ b/build.zig @@ -6,58 +6,6 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const glfw_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - }); - const glfw = b.addLibrary(.{ - .name = "glfw", - .root_module = glfw_module, - }); - glfw_module.addCSourceFiles(.{ .files = &[_][]const u8{ - "ext/glfw/src/cocoa_init.m", - "ext/glfw/src/cocoa_joystick.m", - "ext/glfw/src/cocoa_monitor.m", - "ext/glfw/src/cocoa_time.c", - "ext/glfw/src/cocoa_window.m", - "ext/glfw/src/context.c", - "ext/glfw/src/egl_context.c", - "ext/glfw/src/glx_context.c", - "ext/glfw/src/init.c", - "ext/glfw/src/input.c", - "ext/glfw/src/linux_joystick.c", - "ext/glfw/src/monitor.c", - "ext/glfw/src/nsgl_context.m", - "ext/glfw/src/null_init.c", - "ext/glfw/src/null_joystick.c", - "ext/glfw/src/null_monitor.c", - "ext/glfw/src/null_window.c", - "ext/glfw/src/osmesa_context.c", - "ext/glfw/src/platform.c", - "ext/glfw/src/posix_module.c", - "ext/glfw/src/posix_poll.c", - "ext/glfw/src/posix_thread.c", - "ext/glfw/src/posix_time.c", - "ext/glfw/src/vulkan.c", - "ext/glfw/src/wgl_context.c", - "ext/glfw/src/win32_init.c", - "ext/glfw/src/win32_joystick.c", - "ext/glfw/src/win32_module.c", - "ext/glfw/src/win32_monitor.c", - "ext/glfw/src/win32_thread.c", - "ext/glfw/src/win32_time.c", - "ext/glfw/src/win32_window.c", - "ext/glfw/src/window.c", - "ext/glfw/src/wl_init.c", - "ext/glfw/src/wl_monitor.c", - "ext/glfw/src/wl_window.c", - "ext/glfw/src/x11_init.c", - "ext/glfw/src/x11_monitor.c", - "ext/glfw/src/x11_window.c", - "ext/glfw/src/xkb_unicode.c", - }, .flags = &[_][]const u8{ "-D_GLFW_X11", "-Wall", "-Wextra" } }); - const sideros = b.createModule(.{ .root_source_file = b.path("src/sideros.zig"), .target = target, @@ -88,7 +36,6 @@ pub fn build(b: *std.Build) void { renderer.addImport("ecs", ecs); ecs.addImport("renderer", renderer); - renderer.addIncludePath(b.path("ext/glfw/include")); compileAllShaders(b, renderer); sideros.addImport("mods", mods); @@ -104,10 +51,11 @@ pub fn build(b: *std.Build) void { }), }); exe.root_module.addImport("sideros", sideros); + exe.root_module.addCSourceFile(.{ .file = b.path("ext/xdg-shell.c") }); + exe.root_module.addIncludePath(b.path("ext")); exe.linkSystemLibrary("vulkan"); exe.linkSystemLibrary("wayland-client"); - exe.linkLibrary(glfw); exe.linkLibC(); b.installArtifact(exe); diff --git a/src/c.zig b/src/c.zig new file mode 100644 index 0000000..0251923 --- /dev/null +++ b/src/c.zig @@ -0,0 +1,4 @@ +pub const c = @cImport({ + @cInclude("wayland-client.h"); + @cInclude("xdg-shell.h"); +}); diff --git a/src/main.zig b/src/main.zig index 76ac78a..0d300c3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,73 +4,67 @@ const math = @import("sideros").math; const Input = @import("sideros").Input; const mods = @import("sideros").mods; const ecs = @import("sideros").ecs; -const Renderer = @import("sideros").Renderer; +//const Renderer = @import("sideros").Renderer; +const wayland = @import("wayland.zig"); -fn testSystem2(pool: *ecs.Pool) void { - std.debug.print("{any}\n", .{pool.resources.input.isKeyDown(.a)}); -} +//fn testSystem2(pool: *ecs.Pool) void { +// std.debug.print("{any}\n", .{pool.resources.input.isKeyDown(.a)}); +//} pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); defer if (gpa.deinit() != .ok) @panic("Leaked memory"); - var global_runtime = mods.GlobalRuntime.init(allocator); - defer global_runtime.deinit(); - try global_runtime.addFunction("debug", mods.Wasm.debug); + //var global_runtime = mods.GlobalRuntime.init(allocator); + //defer global_runtime.deinit(); + //try global_runtime.addFunction("debug", mods.Wasm.debug); - const file = try std.fs.cwd().openFile("assets/core.wasm", .{}); - const all = try file.readToEndAlloc(allocator, 1_000_000); // 1 MB - defer allocator.free(all); - var parser = try mods.Parser.init(allocator, all); - defer parser.deinit(); - parser.parseModule() catch |err| { - std.debug.print("[ERROR]: error at byte {x}(0x{x})\n", .{ parser.byte_idx, parser.bytes[parser.byte_idx] }); - return err; - }; - const module = parser.module(); - // defer module.deinit(allocator); + //const file = try std.fs.cwd().openFile("assets/core.wasm", .{}); + //const all = try file.readToEndAlloc(allocator, 1_000_000); // 1 MB + //defer allocator.free(all); + //var parser = try mods.Parser.init(allocator, all); + //defer parser.deinit(); + //parser.parseModule() catch |err| { + // std.debug.print("[ERROR]: error at byte {x}(0x{x})\n", .{ parser.byte_idx, parser.bytes[parser.byte_idx] }); + // return err; + //}; + //const module = parser.module(); + //// defer module.deinit(allocator); - var runtime = try mods.Runtime.init(allocator, module, &global_runtime); - defer runtime.deinit(allocator); + //var runtime = try mods.Runtime.init(allocator, module, &global_runtime); + //defer runtime.deinit(allocator); - var parameters = [_]mods.VM.Value{.{ .i32 = 17 }}; - try runtime.callExternal(allocator, .preinit, ¶meters); - const result = runtime.stack.pop().?; - std.debug.print("Result of preinit: {any}\n", .{result}); - var w = try Renderer.Window.create(800, 600, "sideros"); - defer w.destroy(); + //var parameters = [_]mods.VM.Value{.{ .i32 = 17 }}; + //try runtime.callExternal(allocator, .preinit, ¶meters); + //const result = runtime.stack.pop().?; + //std.debug.print("Result of preinit: {any}\n", .{result}); + //var w = try Renderer.Window.create(800, 600, "sideros"); + //defer w.destroy(); - var r = try Renderer.init(allocator, w); - defer r.deinit(); + //var r = try Renderer.init(allocator, w); + //defer r.deinit(); - const resources = ecs.Resources{ - .window = w, - .renderer = r, - .input = .{ .key_pressed = .{false} ** @intFromEnum(Input.KeyCode.menu) }, - }; + //const resources = ecs.Resources{ + // .window = w, + // .renderer = r, + // .input = .{ .key_pressed = .{false} ** @intFromEnum(Input.KeyCode.menu) }, + //}; - var pool = try ecs.Pool.init(allocator, resources); - defer pool.deinit(); - w.setResources(&pool.resources); - try pool.addSystemGroup(&[_]ecs.System{ - Renderer.render, - }, true); - // try pool.addSystemGroup(&[_]ecs.System{ - // testSystem2, - // }); + //var pool = try ecs.Pool.init(allocator, resources); + //defer pool.deinit(); + //w.setResources(&pool.resources); + //try pool.addSystemGroup(&[_]ecs.System{ + // Renderer.render, + //}, true); + //try pool.addSystemGroup(&[_]ecs.System{ + // testSystem2, + //}); // for (0..1000) |_| { // const entity = try pool.createEntity(); // try pool.addComponent(entity, ecs.components.Position{ .x = 1.0, .y = 0.5, .z = 3.0 }); // try pool.addComponent(entity, ecs.components.Speed{ .speed = 5.0 }); // } - var last_time: f64 = 0.0; - while (!w.shouldClose()) { - const current_time = Renderer.Window.getTime(); - pool.resources.delta_time = current_time - last_time; - last_time = current_time; - Renderer.Window.pollEvents(); - pool.tick(); - } + try wayland.init(allocator); } diff --git a/src/renderer/Renderer.zig b/src/renderer/Renderer.zig index 774b13e..fe247c5 100644 --- a/src/renderer/Renderer.zig +++ b/src/renderer/Renderer.zig @@ -19,10 +19,10 @@ current_frame: u32, vertex_buffer: vk.Buffer, index_buffer: vk.Buffer, -pub fn init(allocator: Allocator, w: Window) !Renderer { +pub fn init(allocator: Allocator, display: ?*anyopaque, s: ?*anyopaque) !Renderer { const instance = try vk.Instance.create(allocator); - const surface = try vk.Surface.create(instance, w); + const surface = try vk.Surface.create(instance, display, s); var physical_device = try vk.PhysicalDevice.pick(allocator, instance); const device = try physical_device.create_device(surface, allocator, 2); @@ -34,7 +34,7 @@ pub fn init(allocator: Allocator, w: Window) !Renderer { const render_pass = try vk.RenderPass(2).create(allocator, device, surface, physical_device); - const swapchain = try vk.Swapchain(2).create(allocator, surface, device, physical_device, w, render_pass); + const swapchain = try vk.Swapchain(2).create(allocator, surface, device, physical_device, render_pass); const graphics_pipeline = try vk.GraphicsPipeline(2).create(device, swapchain, render_pass, vertex_shader, fragment_shader); @@ -75,8 +75,9 @@ pub fn deinit(self: Renderer) void { } // TODO: render is maybe a bad name? something like present() or submit() is better? -pub fn render(pool: *ecs.Pool) anyerror!void { - var renderer = pool.resources.renderer; +//pub fn render(pool: *ecs.Pool) anyerror!void { +pub fn render(renderer: *Renderer) anyerror!void { + //var renderer = pool.resources.renderer; try renderer.device.waitFence(renderer.current_frame); const image = try renderer.swapchain.nextImage(renderer.device, renderer.current_frame); @@ -94,4 +95,6 @@ pub fn render(pool: *ecs.Pool) anyerror!void { try renderer.device.submit(renderer.swapchain, image, renderer.current_frame); renderer.current_frame = (renderer.current_frame + 1) % 2; + + renderer.device.waitIdle(); } diff --git a/src/renderer/Window.zig b/src/renderer/Window.zig index 0bc8986..5513ad7 100644 --- a/src/renderer/Window.zig +++ b/src/renderer/Window.zig @@ -10,9 +10,8 @@ pub const Error = error{ }; pub fn getExtensions() [][*c]const u8 { - var extension_count: u32 = undefined; - const raw: [*c][*c]const u8 = c.glfwGetRequiredInstanceExtensions(&extension_count); - const extensions = raw[0..extension_count]; + const raw: [*c][*c]const u8 = .{"VK_KHR_wayland_surface", "VK_KHR_surface"}; + const extensions = raw[0..2]; return extensions; } @@ -20,25 +19,10 @@ pub fn getExtensions() [][*c]const u8 { title: []const u8, width: usize, height: usize, -raw: *c.GLFWwindow, +raw: *c.wl_display, pub fn create(width: usize, height: usize, title: []const u8) !Window { - if (c.glfwInit() != c.GLFW_TRUE) { - const status = c.glfwGetError(null); - - return switch (status) { - c.GLFW_PLATFORM_UNAVAILABLE => Error.platform_unavailable, - c.GLFW_PLATFORM_ERROR => Error.platform_error, - else => unreachable, - }; - } - - c.glfwWindowHint(c.GLFW_RESIZABLE, c.GLFW_FALSE); - c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); - const raw = c.glfwCreateWindow(@intCast(width), @intCast(height), title.ptr, null, null); - c.glfwShowWindow(raw); - _ = c.glfwSetKeyCallback(raw, keyCallback); - _ = c.glfwSetCursorPosCallback(raw, cursorCallback); + const raw = c.wl_display_connect(null); return Window{ .title = title, @@ -52,12 +36,8 @@ pub fn setResources(self: *Window, resources: *ecs.Resources) void { c.glfwSetWindowUserPointer(self.raw, resources); } -pub fn pollEvents() void { - c.glfwPollEvents(); -} - pub fn shouldClose(self: Window) bool { - return c.glfwWindowShouldClose(self.raw) == c.GLFW_TRUE; + return c.wl_display_dispatch(self.raw) != -1; } pub fn size(self: Window) struct { usize, usize } { @@ -70,8 +50,7 @@ pub fn size(self: Window) struct { usize, usize } { } pub fn destroy(self: Window) void { - c.glfwDestroyWindow(self.raw); - c.glfwTerminate(); + c.wl_display_disconnect(self.raw); } pub fn getTime() f64 { diff --git a/src/renderer/c.zig b/src/renderer/c.zig index d012e4e..c80ef4b 100644 --- a/src/renderer/c.zig +++ b/src/renderer/c.zig @@ -1,5 +1,5 @@ pub const c = @cImport({ - @cDefine("GLFW_INCLUDE_NONE", {}); @cInclude("vulkan/vulkan.h"); - @cInclude("GLFW/glfw3.h"); + @cInclude("vulkan/vulkan_wayland.h"); + @cInclude("wayland-client.h"); }); diff --git a/src/renderer/vulkan.zig b/src/renderer/vulkan.zig index 9a5d149..4e1aa4e 100644 --- a/src/renderer/vulkan.zig +++ b/src/renderer/vulkan.zig @@ -73,7 +73,8 @@ pub const Instance = struct { handle: c.VkInstance, pub fn create(allocator: Allocator) !Instance { - const extensions = Window.getExtensions(); + const extensions = [_][*c]const u8 {c.VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, c.VK_KHR_SURFACE_EXTENSION_NAME}; + //const extensions = [_][:0]const u8 {"VK_KHR_wayland_surface\0", "VK_KHR_surface\0"}; // Querry avaliable extensions size var avaliableExtensionsCount: u32 = 0; @@ -140,7 +141,7 @@ pub const Instance = struct { .sType = c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledExtensionCount = @intCast(extensions.len), - .ppEnabledExtensionNames = extensions.ptr, + .ppEnabledExtensionNames = @ptrCast(extensions[0..]), .enabledLayerCount = @intCast(newLayers.items.len), .ppEnabledLayerNames = newLayers.items.ptr, }; @@ -267,7 +268,7 @@ pub fn RenderPass(comptime n: usize) type { pub fn begin(self: Self, swapchain: Swapchain(n), device: Device(n), image: usize, frame: usize) void { std.debug.assert(frame < n); - const clear_color: c.VkClearValue = .{ .color = .{ .float32 = .{ 0.0, 0.0, 0.0, 1.0 } } }; + const clear_color: c.VkClearValue = .{ .color = .{ .float32 = .{ 1.0, 0.0, 0.0, 1.0 } } }; const begin_info: c.VkRenderPassBeginInfo = .{ .sType = c.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, @@ -604,7 +605,7 @@ pub fn Swapchain(comptime n: usize) type { } // TODO: Allow to recreate so Window can be resized - pub fn create(allocator: Allocator, surface: Surface, device: Device(n), physical_device: PhysicalDevice, w: Window, render_pass: RenderPass(n)) !Self { + pub fn create(allocator: Allocator, surface: Surface, device: Device(n), physical_device: PhysicalDevice, render_pass: RenderPass(n)) !Self { const present_modes = try surface.presentModes(allocator, physical_device); defer allocator.free(present_modes); const capabilities = try surface.capabilities(physical_device); @@ -625,7 +626,7 @@ pub fn Swapchain(comptime n: usize) type { if (capabilities.currentExtent.width != std.math.maxInt(u32)) { extent = capabilities.currentExtent; } else { - const width, const height = w.size(); + const width: u32, const height: u32 = .{800, 600}; extent = .{ .width = @intCast(width), @@ -756,10 +757,16 @@ pub fn Swapchain(comptime n: usize) type { pub const Surface = struct { handle: c.VkSurfaceKHR, - pub fn create(instance: Instance, w: Window) !Surface { + pub fn create(instance: Instance, display: ?*anyopaque, surface: ?*anyopaque) !Surface { var handle: c.VkSurfaceKHR = undefined; - try mapError(c.glfwCreateWindowSurface(instance.handle, w.raw, null, &handle)); - return Surface{ + const create_info: c.VkWaylandSurfaceCreateInfoKHR = .{ + .sType = c.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + .display = @ptrCast(display), + .surface = @ptrCast(surface), + }; + + try mapError(c.vkCreateWaylandSurfaceKHR(instance.handle, &create_info, null, &handle)); + return .{ .handle = handle, }; } @@ -834,7 +841,7 @@ pub fn Device(comptime n: usize) type { } pub fn waitFence(self: Self, frame: usize) !void { - std.debug.assert(frame < n); + //std.debug.assert(frame < n); try mapError(c.vkWaitForFences(self.handle, 1, &self.in_flight_fence[frame], c.VK_TRUE, std.math.maxInt(u64))); try mapError(c.vkResetFences(self.handle, 1, &self.in_flight_fence[frame])); } @@ -917,28 +924,30 @@ pub fn Device(comptime n: usize) type { pub fn submit(self: Self, swapchain: Swapchain(n), image: usize, frame: usize) !void { std.debug.assert(frame < n); - //const wait_semaphores: [1]c.VkSemaphore = .{self.image_available[frame]}; - //const signal_semaphores: [1]c.VkSemaphore = .{self.render_finished[frame]}; - //const swapchains: [1]c.VkSwapchainKHR = .{swapchain.handle}; + const wait_semaphores: [1]c.VkSemaphore = .{self.image_available[frame]}; + const signal_semaphores: [1]c.VkSemaphore = .{self.render_finished[frame]}; + const swapchains: [1]c.VkSwapchainKHR = .{swapchain.handle}; + _ = swapchains; const stages: []const u32 = &[_]u32{c.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; const submit_info: c.VkSubmitInfo = .{ .sType = c.VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, - .pWaitSemaphores = &self.image_available[frame], + .pWaitSemaphores = wait_semaphores[0..].ptr, .pWaitDstStageMask = stages.ptr, .commandBufferCount = 1, .pCommandBuffers = &self.command_buffers[frame], .signalSemaphoreCount = 1, - .pSignalSemaphores = &self.render_finished[frame], + .pSignalSemaphores = signal_semaphores[0..].ptr, }; + _ = c.vkResetFences(self.handle, 1, &self.in_flight_fence[frame]); try mapError(c.vkQueueSubmit(self.graphics_queue, 1, &submit_info, self.in_flight_fence[frame])); const present_info: c.VkPresentInfoKHR = .{ .sType = c.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, - .pWaitSemaphores = &self.render_finished[frame], + .pWaitSemaphores = signal_semaphores[0..].ptr, .swapchainCount = 1, .pSwapchains = &swapchain.handle, .pImageIndices = @ptrCast(&image), diff --git a/src/wayland.zig b/src/wayland.zig new file mode 100644 index 0000000..bde076f --- /dev/null +++ b/src/wayland.zig @@ -0,0 +1,152 @@ +const c = @import("c.zig").c; +const std = @import("std"); +const Renderer = @import("sideros").Renderer; + +var resize = false; +var quit = false; +var new_width: u32 = 0; +var new_height: u32 = 0; + +const State = struct { + compositor: ?*c.wl_compositor = null, + shell: ?*c.xdg_wm_base = null, + surface: ?*c.wl_surface = null, + renderer: *Renderer = undefined, + configured: bool = false, +}; + +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); + } +} + +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); + + state.renderer.render() catch @panic("can't render"); + _ = c.wl_surface_commit(state.surface); +} + +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, +}; + +pub fn init(allocator: std.mem.Allocator) !void { + var state: State = .{}; + 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 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, null); + 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); + } + + var renderer = try Renderer.init(allocator, @ptrCast(display), @ptrCast(surface)); + defer renderer.deinit(); + try renderer.render(); + + state.renderer = &renderer; + + 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); + } +}