aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-11 01:27:17 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-11 11:32:04 +0300
commitb34919056ef006d78fde1a50867d6f050ee58254 (patch)
treee4d3e4c8101ccb65b0d824dffd8143c257021f92
parent31b331a3a53884b41926bd6fee5dbc3505e8acde (diff)
downloadzigway-b34919056ef006d78fde1a50867d6f050ee58254.tar.gz
zigway-b34919056ef006d78fde1a50867d6f050ee58254.zip
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.
-rw-r--r--src/main.zig355
1 files changed, 226 insertions, 129 deletions
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, &registry_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();
- },
- }
-}