From b34919056ef006d78fde1a50867d6f050ee58254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Wed, 11 Sep 2024 01:27:17 +0300 Subject: improve shm hello wayland client * Don't allocate a new buffer on each frame; preallocate two buffers and realloc only on window size change. * Refactor code a bit. * Minor cosmetic changes. --- src/main.zig | 355 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 226 insertions(+), 129 deletions(-) (limited to 'src') diff --git a/src/main.zig b/src/main.zig index 5f83b8e..4902176 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,112 +6,267 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const xdg = wayland.client.xdg; +/// Global Wayland registry context; contains pointers to the globals we expect +/// from the compositor. const RegistryContext = struct { - shm: ?*wl.Shm, - compositor: ?*wl.Compositor, - wm_base: ?*xdg.WmBase, + shm: ?*wl.Shm = null, + compositor: ?*wl.Compositor = null, + wm_base: ?*xdg.WmBase = null, }; -const Window = struct { - shm: *wl.Shm, +var registry_ctx = RegistryContext{}; + +/// A window's buffer data; we will paint the frames to these buffers. +const WindowBuffer = struct { + /// Wayland buffer + wl_buffer: *wl.Buffer, + /// Backing data slice from memory mapping for this buffer + map: []u8, + /// Are we being read by the compositor right now? + busy: bool = false, +}; + +/// A single Window (surface); data and methods we need to tell the compositor +/// to render a surface for us. +pub const Window = struct { + /// The allocator that we were allocated on, needed to destroy ourselves + allocator: mem.Allocator, + /// Wayland surface wl_surface: *wl.Surface, - width: i32, - height: i32, - offset: f32, - last_frame: u32, - run: bool, + /// XDG surface attached to the Wayland surface + xdg_surface: *xdg.Surface = undefined, + /// XDG toplevel data + xdg_toplevel: *xdg.Toplevel = undefined, + /// Buffers A and B; Wayland requires double-buffering + buffers: [2]WindowBuffer = undefined, + /// Memory mapped for both buffers + buffers_map: []align(mem.page_size) u8 = undefined, + /// This window's title + title: [*:0]const u8, + /// Width in pixels + width: i32 = 480, + /// Height in pixels + height: i32 = 360, + /// Offset of our checkerboard animation + offset: f32 = 0.0, + /// Last frame's timestamp in ms + last_frame: u32 = 0, + /// Should we keep this window open? + open: bool = true, + + /// Allocate a new window and all backing resources for it + pub fn create(allocator: mem.Allocator, title: [*:0]const u8) !*Window { + const compositor = registry_ctx.compositor orelse return error.NoWlCompositor; + const wm_base = registry_ctx.wm_base orelse return error.NoXdgWmBase; + + var win = try allocator.create(Window); + win.* = Window{ + .allocator = allocator, + .wl_surface = try compositor.createSurface(), + .title = title, + }; + win.xdg_surface = try wm_base.getXdgSurface(win.wl_surface); + win.xdg_toplevel = try win.xdg_surface.getToplevel(); + + try win.allocBuffers(); + + win.xdg_surface.setListener(*Window, xdgSurfaceListener, win); + win.xdg_toplevel.setListener(*Window, xdgToplevelListener, win); + + win.xdg_toplevel.setTitle(title); + + win.wl_surface.commit(); - pub fn drawFrame(state: *Window) !*wl.Buffer { - const stride = state.width * 4; - const size = stride * state.height; + const wl_cb = try win.wl_surface.frame(); + wl_cb.setListener(*Window, frameListener, win); + + return win; + } + + /// Destroy this window and all its associated resources + pub fn destroy(win: *Window) void { + win.destroyBuffers(); + win.xdg_toplevel.destroy(); + win.xdg_surface.destroy(); + win.wl_surface.destroy(); + win.allocator.destroy(win); + } + + /// Allocate new buffers for this window + fn allocBuffers(win: *Window) !void { + const stride = win.width * 4; + const buffer_size = stride * win.height; + const total_size = buffer_size * 2; // we need two buffers const fd = try posix.memfd_create("zigway", 0); defer posix.close(fd); - try posix.ftruncate(fd, @intCast(size)); - var data = try posix.mmap( + try posix.ftruncate(fd, @intCast(total_size)); + win.buffers_map = try posix.mmap( null, - @intCast(size), + @intCast(total_size), posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0, ); - defer posix.munmap(data); - const pool = try state.shm.createPool(fd, size); + const shm = registry_ctx.shm orelse return error.NoShm; + const pool = try shm.createPool(fd, total_size); defer pool.destroy(); - const buffer = try pool.createBuffer(0, state.width, state.height, stride, .argb8888); - - const canvas: *[]u32 = @ptrCast(&data); - const box_side = 12; - var offset: u32 = @intFromFloat(state.offset); - offset = @rem(offset, box_side); - const height: u32 = @intCast(state.height); - const width: u32 = @intCast(state.width); - for (0..height) |y| { - for (0..width) |x| { - if ((x + offset + (y + offset) / box_side * box_side) % (box_side * 2) < box_side) { - canvas.*[y * width + x] = 0xFF0E0E0E; - } else { - canvas.*[y * width + x] = 0xFF2F2F2F; - } + inline for (&win.buffers, 0..win.buffers.len) |*buffer, i| { + buffer.wl_buffer = try pool.createBuffer( + buffer_size *% @as(i32, i), + win.width, + win.height, + stride, + .argb8888, + ); + const bsize: usize = @intCast(buffer_size); + buffer.busy = false; + buffer.map = win.buffers_map[bsize *% i .. bsize *% (i + 1)]; + buffer.wl_buffer.setListener(*WindowBuffer, bufferListener, buffer); + } + } + + /// Destroy this window's buffers + fn destroyBuffers(win: *Window) void { + win.buffers[0].wl_buffer.destroy(); + win.buffers[1].wl_buffer.destroy(); + posix.munmap(win.buffers_map); + } + + /// Draw a new frame to the first free buffer and return it + pub fn drawFrame(win: *Window) !*wl.Buffer { + var buffer: *WindowBuffer = undefined; + if (!win.buffers[0].busy) { + buffer = &win.buffers[0]; + } else if (!win.buffers[1].busy) { + buffer = &win.buffers[1]; + } else { + return error.NoBuffersAvaiable; + } + + const canvas: *[]u32 = @ptrCast(&buffer.map); + const box_side = 16; + var offset: i32 = @intFromFloat(win.offset); + offset = @mod(offset, box_side); + var y: i32 = 0; + while (y < win.height) : (y += 1) { + var x: i32 = 0; + while (x < win.width) : (x += 1) { + const i: usize = @intCast(y * win.width + x); + const side = @mod( + x + offset + @divFloor(y + offset, box_side) * box_side, + (box_side * 2), + ); + canvas.*[i] = if (side < box_side) 0xFF0E0E0E else 0xFF2F2F2F; } } - buffer.setListener(?*void, bufferListener, null); - return buffer; + buffer.busy = true; + return buffer.wl_buffer; + } + + /// Resize this window and reallocate the buffers if needed + fn resize(win: *Window, width: i32, height: i32) !void { + win.height = if (height != 0) height else win.height; + win.width = if (width != 0) width else win.width; + if (width == 0 and height == 0) return; + win.destroyBuffers(); + try win.allocBuffers(); + } + + /// XDG Surface callbacks + fn xdgSurfaceListener(xdg_surface: *xdg.Surface, event: xdg.Surface.Event, win: *Window) void { + switch (event) { + .configure => |configure| { + xdg_surface.ackConfigure(configure.serial); + brk: { + const buffer = win.drawFrame() catch |err| { + std.debug.print("error drawing frame {}\n", .{err}); + break :brk; + }; + win.wl_surface.attach(buffer, 0, 0); + } + win.wl_surface.commit(); + }, + } + } + + /// XDG Toplevel callbacks + fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, win: *Window) void { + switch (event) { + .configure => |configure| { + win.resize(configure.width, configure.height) catch |err| { + std.debug.print("error resizing window {}\n", .{err}); + return; + }; + }, + .close => win.open = false, + } + } + + /// Wayland buffer callbacks + fn bufferListener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *WindowBuffer) void { + switch (event) { + .release => { + buffer.busy = false; + }, + } + } + + /// Wayland frame callback + fn frameListener(wl_cb: *wl.Callback, event: wl.Callback.Event, win: *Window) void { + switch (event) { + .done => |done| { + wl_cb.destroy(); + const new_cb = win.wl_surface.frame() catch |err| { + std.debug.print("unable to get frame {}\n", .{err}); + win.open = false; + return; + }; + new_cb.setListener(*Window, frameListener, win); + + if (win.last_frame != 0) { + const elapsed: f32 = @floatFromInt(done.callback_data -% win.last_frame); + win.offset -= elapsed / 1000.0 * 32.0; + } + defer win.last_frame = done.callback_data; + + const wl_buffer = win.drawFrame() catch |err| { + std.debug.print("error drawing frame {}\n", .{err}); + return; + }; + win.wl_surface.attach(wl_buffer, 0, 0); + win.wl_surface.damageBuffer(0, 0, std.math.maxInt(i32), std.math.maxInt(i32)); + win.wl_surface.commit(); + }, + } } }; pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); const display = try wl.Display.connect(null); defer display.disconnect(); const registry = try display.getRegistry(); - var ctx = RegistryContext{ - .shm = null, - .compositor = null, - .wm_base = null, - }; - - registry.setListener(*RegistryContext, registryListener, &ctx); + registry.setListener(*RegistryContext, registryListener, ®istry_ctx); if (display.roundtrip() != .SUCCESS) return error.RoundTripFailed; - const compositor = ctx.compositor orelse return error.NoWlCompositor; - const wm_base = ctx.wm_base orelse return error.NoXdgWmBase; - - var state = Window{ - .shm = ctx.shm orelse return error.NoWlShm, - .wl_surface = try compositor.createSurface(), - .width = 480, - .height = 360, - .offset = 0.0, - .last_frame = 0, - .run = true, - }; - defer state.wl_surface.destroy(); - - const xdg_surface = try wm_base.getXdgSurface(state.wl_surface); - defer xdg_surface.destroy(); - const xdg_toplevel = try xdg_surface.getToplevel(); - defer xdg_toplevel.destroy(); - - xdg_surface.setListener(*Window, xdgSurfaceListener, &state); - xdg_toplevel.setListener(*Window, xdgToplevelListener, &state); + const wm_base = registry_ctx.wm_base orelse return error.NoXdgWmBase; wm_base.setListener(?*void, xdgWmBaseListener, null); - xdg_toplevel.setTitle("Hello, wayland!"); - - state.wl_surface.commit(); + const win = try Window.create(allocator, "Hello, wayland!"); + defer win.destroy(); - const wl_cb = try state.wl_surface.frame(); - wl_cb.setListener(*Window, frameListener, &state); - - while (state.run) { + while (win.open) { if (display.dispatch() != .SUCCESS) return error.DispatchFailed; } } +/// Global registry callback fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *RegistryContext) void { switch (event) { .global => |global| { @@ -132,31 +287,7 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *Regi } } -fn xdgSurfaceListener(xdg_surface: *xdg.Surface, event: xdg.Surface.Event, state: *Window) void { - switch (event) { - .configure => |configure| { - xdg_surface.ackConfigure(configure.serial); - brk: { - const buffer = state.drawFrame() catch |err| { - std.debug.print("error drawing frame {}\n", .{err}); - break :brk; - }; - state.wl_surface.attach(buffer, 0, 0); - } - state.wl_surface.commit(); - }, - } -} - -fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, state: *Window) void { - switch (event) { - .configure => |configure| { - state.height = if (configure.height != 0) configure.height else state.height; - state.width = if (configure.width != 0) configure.width else state.width; - }, - .close => state.run = false, - } -} +/// XDG Wm Base callback fn xdgWmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: ?*void) void { switch (event) { .ping => |ping| { @@ -164,37 +295,3 @@ fn xdgWmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: ?*void) v }, } } - -fn bufferListener(buffer: *wl.Buffer, event: wl.Buffer.Event, _: ?*void) void { - switch (event) { - .release => buffer.destroy(), - } -} - -fn frameListener(wl_cb: *wl.Callback, event: wl.Callback.Event, state: *Window) void { - switch (event) { - .done => |done| { - wl_cb.destroy(); - const new_cb = state.wl_surface.frame() catch |err| { - std.debug.print("unable to get frame {}\n", .{err}); - state.run = false; - return; - }; - new_cb.setListener(*Window, frameListener, state); - - if (state.last_frame != 0) { - const elapsed: f32 = @floatFromInt(done.callback_data -% state.last_frame); - state.offset += elapsed / 1000.0 * 48.0; - } - defer state.last_frame = done.callback_data; - - const buffer = state.drawFrame() catch |err| { - std.debug.print("error drawing frame {}\n", .{err}); - return; - }; - state.wl_surface.attach(buffer, 0, 0); - state.wl_surface.damageBuffer(0, 0, std.math.maxInt(i32), std.math.maxInt(i32)); - state.wl_surface.commit(); - }, - } -} -- cgit v1.2.3