From d787e2c2cc796170325e4bf2591d0f612bff6e04 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 26 Mar 2020 21:32:30 +0100 Subject: [PATCH] Initial atomic layout update implementation --- src/c.zig | 3 +- src/cursor.zig | 12 +++--- src/keyboard.zig | 4 +- src/output.zig | 104 +++++++++++++++++++++++++++++++++----------- src/root.zig | 84 +++++++++++++++++++++++++++++++++++- src/view.zig | 109 ++++++++++++++++++++++++++++++++++++++++------- 6 files changed, 264 insertions(+), 52 deletions(-) diff --git a/src/c.zig b/src/c.zig index 3073111..473a139 100644 --- a/src/c.zig +++ b/src/c.zig @@ -5,8 +5,9 @@ pub const c = @cImport({ @cInclude("wayland-server-core.h"); //@cInclude("wlr/backend.h"); //@cInclude("wlr/render/wlr_renderer.h"); - @cInclude("wlr/types/wlr_cursor.h"); + @cInclude("wlr/types/wlr_buffer.h"); @cInclude("wlr/types/wlr_compositor.h"); + @cInclude("wlr/types/wlr_cursor.h"); @cInclude("wlr/types/wlr_data_device.h"); @cInclude("wlr/types/wlr_input_device.h"); @cInclude("wlr/types/wlr_keyboard.h"); diff --git a/src/cursor.zig b/src/cursor.zig index e1fb348..af39e73 100644 --- a/src/cursor.zig +++ b/src/cursor.zig @@ -98,8 +98,8 @@ pub const Cursor = struct { // Move the grabbed view to the new position. // TODO: log on null if (self.grabbed_view) |view| { - view.x = @floatToInt(c_int, self.wlr_cursor.x - self.grab_x); - view.y = @floatToInt(c_int, self.wlr_cursor.y - self.grab_y); + view.current_state.x = @floatToInt(c_int, self.wlr_cursor.x - self.grab_x); + view.current_state.y = @floatToInt(c_int, self.wlr_cursor.y - self.grab_y); } } @@ -119,8 +119,8 @@ pub const Cursor = struct { const dx: f64 = self.wlr_cursor.x - self.grab_x; const dy: f64 = self.wlr_cursor.y - self.grab_y; - var x: f64 = @intToFloat(f64, view.x); - var y: f64 = @intToFloat(f64, view.y); + var x: f64 = @intToFloat(f64, view.current_state.x); + var y: f64 = @intToFloat(f64, view.current_state.y); var width = @intToFloat(f64, self.grab_width); var height = @intToFloat(f64, self.grab_height); @@ -143,8 +143,8 @@ pub const Cursor = struct { } else if (self.resize_edges & @intCast(u32, c.WLR_EDGE_RIGHT) != 0) { width += dx; } - view.x = @floatToInt(c_int, x); - view.y = @floatToInt(c_int, y); + view.current_state.x = @floatToInt(c_int, x); + view.current_state.y = @floatToInt(c_int, y); _ = c.wlr_xdg_toplevel_set_size( view.wlr_xdg_surface, @floatToInt(u32, width), diff --git a/src/keyboard.zig b/src/keyboard.zig index 1130052..4229e0d 100644 --- a/src/keyboard.zig +++ b/src/keyboard.zig @@ -16,7 +16,7 @@ pub const Keyboard = struct { pub fn init(self: *Self, seat: *Seat, device: *c.wlr_input_device) !void { self.seat = seat; self.device = device; - self.wlr_keyboard = device.unnamed_37.keyboard; + self.wlr_keyboard = device.unnamed_133.keyboard; // We need to prepare an XKB keymap and assign it to the keyboard. This // assumes the defaults (e.g. layout = "us"). @@ -77,7 +77,7 @@ pub const Keyboard = struct { @alignCast(@alignOf(*c.wlr_event_keyboard_key), data), ); - const wlr_keyboard: *c.wlr_keyboard = keyboard.device.unnamed_37.keyboard; + const wlr_keyboard: *c.wlr_keyboard = keyboard.device.unnamed_133.keyboard; // Translate libinput keycode -> xkbcommon const keycode = event.keycode + 8; diff --git a/src/output.zig b/src/output.zig index 9c79f91..12fcdc3 100644 --- a/src/output.zig +++ b/src/output.zig @@ -84,19 +84,12 @@ pub const Output = struct { var it = output.root.views.last; while (it) |node| : (it = node.prev) { const view = &node.data; + // TODO: remove this check and move unmaped views back to unmaped TailQueue if (!view.mapped) { // An unmapped view should not be rendered. continue; } - var rdata = RenderData{ - .output = output.wlr_output, - .view = view, - .renderer = renderer, - .when = &now, - }; - // This calls our render_surface function for each surface among the - // xdg_surface's toplevel and popups. - c.wlr_xdg_surface_for_each_surface(view.wlr_xdg_surface, renderSurface, &rdata); + output.renderView(view, &now); } // Hardware cursors are rendered by the GPU on a separate plane, and can be @@ -114,9 +107,56 @@ pub const Output = struct { _ = c.wlr_output_commit(output.wlr_output); } - fn renderSurface(opt_surface: ?*c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void { + fn renderView(self: *Self, view: *View, now: *c.struct_timespec) void { + // If we have a stashed buffer, we are in the middle of a transaction + // and need to render that buffer until the transaction is complete. + if (view.stashed_buffer) |buffer| { + var box = c.wlr_box{ + .x = view.current_state.x, + .y = view.current_state.y, + .width = @intCast(c_int, view.current_state.width), + .height = @intCast(c_int, view.current_state.height), + }; + + // Scale the box to the output's current scaling factor + scaleBox(&box, self.wlr_output.scale); + + var matrix: [9]f32 = undefined; + c.wlr_matrix_project_box( + &matrix, + &box, + c.enum_wl_output_transform.WL_OUTPUT_TRANSFORM_NORMAL, + 0.0, + &self.wlr_output.transform_matrix, + ); + + // This takes our matrix, the texture, and an alpha, and performs the actual + // rendering on the GPU. + _ = c.wlr_render_texture_with_matrix( + self.root.server.wlr_renderer, + buffer.texture, + &matrix, + 1.0, + ); + } else { + // Since there is no stashed buffer, we are not in the middle of + // a transaction and may simply render each toplevel surface. + var rdata = RenderData{ + .output = self.wlr_output, + .view = view, + .renderer = self.root.server.wlr_renderer, + .when = now, + }; + + // This calls our render_surface function for each surface among the + // xdg_surface's toplevel and popups. + c.wlr_xdg_surface_for_each_surface(view.wlr_xdg_surface, renderSurface, &rdata); + } + } + + fn renderSurface(_surface: ?*c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void { // wlroots says this will never be null - const surface = opt_surface.?; + const surface = _surface.?; // This function is called for every surface that needs to be rendered. const rdata = @ptrCast(*RenderData, @alignCast(@alignOf(RenderData), data)); const view = rdata.view; @@ -139,27 +179,23 @@ pub const Output = struct { var ox: f64 = 0.0; var oy: f64 = 0.0; c.wlr_output_layout_output_coords(view.root.wlr_output_layout, output, &ox, &oy); - ox += @intToFloat(f64, view.x + sx); - oy += @intToFloat(f64, view.y + sy); + ox += @intToFloat(f64, view.current_state.x + sx); + oy += @intToFloat(f64, view.current_state.y + sy); - // We also have to apply the scale factor for HiDPI outputs. This is only - // part of the puzzle, TinyWL does not fully support HiDPI. - const box = c.wlr_box{ - .x = @floatToInt(c_int, ox * output.scale), - .y = @floatToInt(c_int, oy * output.scale), - .width = @floatToInt(c_int, @intToFloat(f32, surface.current.width) * output.scale), - .height = @floatToInt(c_int, @intToFloat(f32, surface.current.height) * output.scale), + var box = c.wlr_box{ + .x = @floatToInt(c_int, ox), + .y = @floatToInt(c_int, oy), + .width = @intCast(c_int, surface.current.width), + .height = @intCast(c_int, surface.current.height), }; - // Those familiar with OpenGL are also familiar with the role of matricies - // in graphics programming. We need to prepare a matrix to render the view - // with. wlr_matrix_project_box is a helper which takes a box with a desired + // Scale the box to the output's current scaling factor + scaleBox(&box, output.scale); + + // wlr_matrix_project_box is a helper which takes a box with a desired // x, y coordinates, width and height, and an output geometry, then // prepares an orthographic projection and multiplies the necessary // transforms to produce a model-view-projection matrix. - // - // Naturally you can do this any way you like, for example to make a 3D - // compositor. var matrix: [9]f32 = undefined; const transform = c.wlr_output_transform_invert(surface.current.transform); c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &output.transform_matrix); @@ -173,3 +209,19 @@ pub const Output = struct { c.wlr_surface_send_frame_done(surface, rdata.when); } }; + +/// Scale a wlr_box, taking the possibility of fractional scaling into account. +fn scaleBox(box: *c.wlr_box, scale: f64) void { + box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale)); + box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale)); + box.width = scaleLength(box.width, box.x, scale); + box.height = scaleLength(box.height, box.x, scale); +} + +/// Scales a width/height. +/// +/// This might seem overly complex, but it needs to work for fractional scaling. +fn scaleLength(length: c_int, offset: c_int, scale: f64) c_int { + return @floatToInt(c_int, @round(@intToFloat(f64, offset + length) * scale) - + @round(@intToFloat(f64, offset) * scale)); +} diff --git a/src/root.zig b/src/root.zig index fca4591..e31d2e1 100644 --- a/src/root.zig +++ b/src/root.zig @@ -17,9 +17,15 @@ pub const Root = struct { // Must stay ordered, first N views in list are the masters views: std.TailQueue(View), + unmapped_views: std.TailQueue(View), + + // Number of pending configures sent in the current transaction. + // A value of 0 means there is no current transaction. + pending_count: u32, pub fn init(self: *Self, server: *Server) !void { self.server = server; + self.pending_count = 0; // Create an output layout, which a wlroots utility for working with an // arrangement of screens in a physical layout. @@ -29,6 +35,7 @@ pub const Root = struct { self.outputs = std.TailQueue(Output).init(); self.views = std.TailQueue(View).init(); + self.unmapped_views = std.TailQueue(View).init(); } pub fn destroy(self: *Self) void { @@ -45,7 +52,7 @@ pub const Root = struct { pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void { const node = self.views.allocateNode(self.server.allocator) catch unreachable; node.data.init(self, wlr_xdg_surface); - self.views.append(node); + self.unmapped_views.append(node); } /// Finds the top most view under the output layout coordinates lx, ly @@ -59,4 +66,79 @@ pub const Root = struct { } return null; } + + pub fn arrange(self: *Self) void { + if (self.views.len == 0) { + return; + } + // Super basic vertical layout for now, no master/slave stuff + // This can't return null if pass null as the reference + const output_box: *c.wlr_box = c.wlr_output_layout_get_box(self.wlr_output_layout, null); + const new_height = output_box.height; + // Allow for a 10px gap + const num_views = @intCast(c_int, self.views.len); + const new_width = @divTrunc(output_box.width, num_views) - (num_views - 1) * 10; + + var x: c_int = 0; + var y: c_int = 0; + + var it = self.views.first; + while (it) |node| : (it = node.next) { + const view = &node.data; + view.pending_state.x = x; + view.pending_state.y = y; + view.pending_state.width = @intCast(u32, new_width); + view.pending_state.height = @intCast(u32, new_height); + + x += new_width + 10; + } + + self.startTransaction(); + } + + /// Initiate an atomic change to the layout. This change will not be + /// applied until all affected clients ack a configure and commit a buffer. + fn startTransaction(self: *Self) void { + std.debug.assert(self.pending_count == 0); + + var it = self.views.first; + while (it) |node| : (it = node.next) { + const view = &node.data; + if (view.needsConfigure()) { + view.configurePending(); + self.pending_count += 1; + + // We save the current buffer, so we can send an early + // frame done event to give the client a head start on + // redrawing. + view.sendFrameDone(); + } + view.stashBuffer(); + } + + // TODO: start a timer and handle timeout waiting for all clients to ack + } + + pub fn notifyConfigured(self: *Self) void { + self.pending_count -= 1; + if (self.pending_count == 0) { + self.commitTransaction(); + } + } + + /// Apply the pending state and drop stashed buffers. This means that + /// the next frame drawn will be the post-transaction state of the + /// layout. Must only be called after all clients have configured for + /// the new layout. + fn commitTransaction(self: *Self) void { + // TODO: apply damage properly + var it = self.views.first; + while (it) |node| : (it = node.next) { + const view = &node.data; + + // TODO: handle views that timed out + view.current_state = view.pending_state; + view.dropStashedBuffer(); + } + } }; diff --git a/src/view.zig b/src/view.zig index b04efe4..cd91773 100644 --- a/src/view.zig +++ b/src/view.zig @@ -3,6 +3,13 @@ const c = @import("c.zig").c; const Root = @import("root.zig").Root; +pub const ViewState = struct { + x: i32, + y: i32, + width: u32, + height: u32, +}; + pub const View = struct { const Self = @This(); @@ -10,12 +17,20 @@ pub const View = struct { wlr_xdg_surface: *c.wlr_xdg_surface, mapped: bool, - x: c_int, - y: c_int, + + current_state: ViewState, + // TODO: make this a ?ViewState + pending_state: ViewState, + + pending_serial: ?u32, + + // This is what we render while a transaction is in progress + stashed_buffer: ?*c.wlr_buffer, listen_map: c.wl_listener, listen_unmap: c.wl_listener, listen_destroy: c.wl_listener, + listen_commit: c.wl_listener, // listen_request_move: c.wl_listener, // listen_request_resize: c.wl_listener, @@ -24,8 +39,13 @@ pub const View = struct { self.wlr_xdg_surface = wlr_xdg_surface; self.mapped = false; - self.x = 0; - self.y = 0; + self.current_state = ViewState{ + .x = 0, + .y = 0, + .height = 0, + .width = 0, + }; + self.stashed_buffer = null; self.listen_map.notify = handleMap; c.wl_signal_add(&self.wlr_xdg_surface.events.map, &self.listen_map); @@ -36,16 +56,62 @@ pub const View = struct { self.listen_destroy.notify = handleDestroy; c.wl_signal_add(&self.wlr_xdg_surface.events.destroy, &self.listen_destroy); + self.listen_commit.notify = handleCommit; + c.wl_signal_add(&self.wlr_xdg_surface.surface.*.events.commit, &self.listen_commit); + // const toplevel = xdg_surface.unnamed_160.toplevel; // c.wl_signal_add(&toplevel.events.request_move, &view.request_move); // c.wl_signal_add(&toplevel.events.request_resize, &view.request_resize); } + pub fn needsConfigure(self: *const Self) bool { + return self.pending_state.width != self.current_state.width or + self.pending_state.height != self.current_state.height; + } + + pub fn configurePending(self: *Self) void { + self.pending_serial = c.wlr_xdg_toplevel_set_size( + self.wlr_xdg_surface, + self.pending_state.width, + self.pending_state.height, + ); + } + + pub fn sendFrameDone(self: *Self) void { + var now: c.struct_timespec = undefined; + _ = c.clock_gettime(c.CLOCK_MONOTONIC, &now); + c.wlr_surface_send_frame_done(self.wlr_xdg_surface.surface, &now); + } + + pub fn dropStashedBuffer(self: *Self) void { + std.debug.warn("drop stashed\n", .{}); + // TODO: log debug error + if (self.stashed_buffer) |buffer| { + c.wlr_buffer_unref(buffer); + std.debug.warn("drop stashed\n", .{}); + self.stashed_buffer = null; + } + } + + pub fn stashBuffer(self: *Self) void { + // TODO: log debug error if there is already a saved buffer + const wlr_surface = self.wlr_xdg_surface.surface; + if (c.wlr_surface_has_buffer(wlr_surface)) { + _ = c.wlr_buffer_ref(wlr_surface.*.buffer); + self.stashed_buffer = wlr_surface.*.buffer; + } + } + fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // Called when the surface is mapped, or ready to display on-screen. const view = @fieldParentPtr(View, "listen_map", listener.?); view.mapped = true; view.focus(view.wlr_xdg_surface.surface); + view.root.arrange(); + + const node = @fieldParentPtr(std.TailQueue(View).Node, "data", view); + view.root.unmapped_views.remove(node); + view.root.views.prepend(node); } fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { @@ -68,6 +134,17 @@ pub const View = struct { root.views.destroyNode(target, root.server.allocator); } + fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { + const view = @fieldParentPtr(View, "listen_commit", listener.?); + if (view.pending_serial) |s| { + if (s == view.wlr_xdg_surface.configure_serial) { + view.root.notifyConfigured(); + view.pending_serial = null; + } + } + // TODO: check for unexpected change in size and react as needed + } + // fn xdgToplevelRequestMove(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // // ignore for now // } @@ -94,17 +171,17 @@ pub const View = struct { _ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false); } - // Find the node - var it = root.views.first; - const target = while (it) |node| : (it = node.next) { - if (&node.data == self) { - break node; - } - } else unreachable; + //// Find the node + //var it = root.views.first; + //const target = while (it) |node| : (it = node.next) { + // if (&node.data == self) { + // break node; + // } + //} else unreachable; - // Move the view to the front - root.views.remove(target); - root.views.prepend(target); + //// Move the view to the front + //root.views.remove(target); + //root.views.prepend(target); // Activate the new surface _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, true); @@ -128,8 +205,8 @@ pub const View = struct { // coordinates lx and ly (in output Layout Coordinates). If so, it sets the // surface pointer to that wlr_surface and the sx and sy coordinates to the // coordinates relative to that surface's top-left corner. - const view_sx = lx - @intToFloat(f64, self.x); - const view_sy = ly - @intToFloat(f64, self.y); + const view_sx = lx - @intToFloat(f64, self.current_state.x); + const view_sy = ly - @intToFloat(f64, self.current_state.y); // This variable seems to have been unsued in TinyWL // struct wlr_surface_state *state = &view->xdg_surface->surface->current;