render: use wlr_scene to render views
This commit is contained in:
		| @ -885,7 +885,7 @@ fn xwaylandOverrideRedirectSurfaceAt(lx: f64, ly: f64) ?SurfaceAtResult { | ||||
| } | ||||
|  | ||||
| fn surfaceAtFilter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and view.current.tags & filter_tags != 0; | ||||
|     return view.tree.node.enabled and view.current.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
| pub fn enterMode(self: *Self, mode: enum { move, resize }, view: *View) void { | ||||
|  | ||||
| @ -64,6 +64,10 @@ layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** | ||||
| /// TODO: this should be part of the output's State | ||||
| usable_box: wlr.Box, | ||||
|  | ||||
| /// Scene node representing the entire output. | ||||
| /// Position must be updated when the output is moved in the layout. | ||||
| tree: *wlr.SceneTree, | ||||
|  | ||||
| /// The top of the stack is the "most important" view. | ||||
| views: ViewStack(View) = .{}, | ||||
|  | ||||
| @ -144,6 +148,7 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void { | ||||
|  | ||||
|     self.* = .{ | ||||
|         .wlr_output = wlr_output, | ||||
|         .tree = try server.root.scene.tree.createSceneTree(), | ||||
|         .usable_box = undefined, | ||||
|     }; | ||||
|     wlr_output.data = @ptrToInt(self); | ||||
| @ -208,7 +213,7 @@ pub fn sendLayoutNameClear(self: Self) void { | ||||
| } | ||||
|  | ||||
| pub fn arrangeFilter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and !view.pending.float and !view.pending.fullscreen and | ||||
|     return view.tree.node.enabled and !view.pending.float and !view.pending.fullscreen and | ||||
|         view.pending.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -77,7 +77,7 @@ pub fn sendViewTags(self: Self) void { | ||||
|  | ||||
|     var it = self.output.views.first; | ||||
|     while (it) |node| : (it = node.next) { | ||||
|         if (node.view.surface == null) continue; | ||||
|         if (!node.view.tree.node.enabled) continue; | ||||
|         view_tags.append(node.view.current.tags) catch { | ||||
|             self.output_status.postNoMemory(); | ||||
|             log.err("out of memory", .{}); | ||||
|  | ||||
| @ -32,6 +32,8 @@ const ViewStack = @import("view_stack.zig").ViewStack; | ||||
| const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig"); | ||||
| const DragIcon = @import("DragIcon.zig"); | ||||
|  | ||||
| scene: *wlr.Scene, | ||||
|  | ||||
| new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput), | ||||
|  | ||||
| output_layout: *wlr.OutputLayout, | ||||
| @ -77,6 +79,11 @@ pub fn init(self: *Self) !void { | ||||
|     const output_layout = try wlr.OutputLayout.create(); | ||||
|     errdefer output_layout.destroy(); | ||||
|  | ||||
|     const scene = try wlr.Scene.create(); | ||||
|     errdefer scene.tree.node.destroy(); | ||||
|  | ||||
|     try scene.attachOutputLayout(output_layout); | ||||
|  | ||||
|     _ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout); | ||||
|  | ||||
|     const event_loop = server.wl_server.getEventLoop(); | ||||
| @ -85,12 +92,14 @@ pub fn init(self: *Self) !void { | ||||
|  | ||||
|     const noop_wlr_output = try server.headless_backend.headlessAddOutput(1920, 1080); | ||||
|     self.* = .{ | ||||
|         .scene = scene, | ||||
|         .output_layout = output_layout, | ||||
|         .output_manager = try wlr.OutputManagerV1.create(server.wl_server), | ||||
|         .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server), | ||||
|         .transaction_timer = transaction_timer, | ||||
|         .noop_output = .{ | ||||
|             .wlr_output = noop_wlr_output, | ||||
|             .tree = try scene.tree.createSceneTree(), | ||||
|             .usable_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 }, | ||||
|         }, | ||||
|     }; | ||||
| @ -104,6 +113,7 @@ pub fn init(self: *Self) !void { | ||||
| } | ||||
|  | ||||
| pub fn deinit(self: *Self) void { | ||||
|     self.scene.tree.node.destroy(); | ||||
|     self.output_layout.destroy(); | ||||
|     self.transaction_timer.remove(); | ||||
| } | ||||
| @ -204,7 +214,11 @@ pub fn addOutput(self: *Self, output: *Output) void { | ||||
|     // This aarranges outputs from left-to-right in the order they appear. The | ||||
|     // wlr-output-management protocol may be used to modify this arrangement. | ||||
|     // This also creates a wl_output global which is advertised to clients. | ||||
|     self.output_layout.addAuto(node.data.wlr_output); | ||||
|     self.output_layout.addAuto(output.wlr_output); | ||||
|  | ||||
|     const layout_output = self.output_layout.get(output.wlr_output).?; | ||||
|     output.tree.node.setEnabled(true); | ||||
|     output.tree.node.setPosition(layout_output.x, layout_output.y); | ||||
|  | ||||
|     // If we previously had no real outputs, move focus from the noop output | ||||
|     // to the new one. | ||||
| @ -271,7 +285,7 @@ pub fn startTransaction(self: *Self) void { | ||||
|         while (view_it) |view_node| : (view_it = view_node.next) { | ||||
|             const view = &view_node.view; | ||||
|  | ||||
|             if (view.surface == null) continue; | ||||
|             if (!view.tree.node.enabled) continue; | ||||
|  | ||||
|             if (view.shouldTrackConfigure()) { | ||||
|                 // Clear the serial in case this transaction is interrupting a prior one. | ||||
| @ -366,7 +380,7 @@ fn commitTransaction(self: *Self) void { | ||||
|             const view = &view_node.view; | ||||
|             view_it = view_node.next; | ||||
|  | ||||
|             if (view.surface == null) { | ||||
|             if (!view.tree.node.enabled) { | ||||
|                 view.dropSavedBuffers(); | ||||
|                 view.output.views.remove(view_node); | ||||
|                 if (view.destroying) view.destroy(); | ||||
| @ -383,6 +397,8 @@ fn commitTransaction(self: *Self) void { | ||||
|             if (view.pending.urgent and view_tags_changed) urgent_tags_dirty = true; | ||||
|             view.current = view.pending; | ||||
|  | ||||
|             view.tree.node.setPosition(view.current.box.x, view.current.box.y); | ||||
|  | ||||
|             view.dropSavedBuffers(); | ||||
|         } | ||||
|  | ||||
| @ -459,10 +475,13 @@ fn processOutputConfig( | ||||
|                     if (head.state.enabled) { | ||||
|                         // Just updates the output's position if it is already in the layout | ||||
|                         self.output_layout.add(output.wlr_output, head.state.x, head.state.y); | ||||
|                         output.tree.node.setEnabled(true); | ||||
|                         output.tree.node.setPosition(head.state.x, head.state.y); | ||||
|                         output.arrangeLayers(.mapped); | ||||
|                     } else { | ||||
|                         self.removeOutput(output); | ||||
|                         self.output_layout.remove(output.wlr_output); | ||||
|                         output.tree.node.setEnabled(false); | ||||
|                     } | ||||
|                 } else { | ||||
|                     std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{ | ||||
|  | ||||
| @ -210,7 +210,7 @@ pub fn focus(self: *Self, _target: ?*View) void { | ||||
| } | ||||
|  | ||||
| fn pendingFilter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and view.pending.tags & filter_tags != 0; | ||||
|     return view.tree.node.enabled and view.pending.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
| /// Switch focus to the target, handling unfocus and input inhibition | ||||
| @ -222,7 +222,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { | ||||
|  | ||||
|     // Obtain the target surface | ||||
|     const target_surface = switch (new_focus) { | ||||
|         .view => |target_view| target_view.surface.?, | ||||
|         .view => |target_view| target_view.rootSurface(), | ||||
|         .xwayland_override_redirect => |target_or| target_or.xwayland_surface.surface, | ||||
|         .layer => |target_layer| target_layer.wlr_layer_surface.surface, | ||||
|         .lock_surface => |lock_surface| lock_surface.wlr_lock_surface.surface, | ||||
|  | ||||
| @ -48,9 +48,7 @@ const Impl = union(enum) { | ||||
| }; | ||||
|  | ||||
| const State = struct { | ||||
|     /// The output-relative effective coordinates and effective dimensions of the view. The | ||||
|     /// surface itself may have other dimensions which are stored in the | ||||
|     /// surface_box member. | ||||
|     /// The output-relative coordinates of the view and dimensions requested by river. | ||||
|     box: wlr.Box = wlr.Box{ .x = 0, .y = 0, .width = 0, .height = 0 }, | ||||
|  | ||||
|     /// The tags of the view, as a bitmask | ||||
| @ -78,8 +76,7 @@ impl: Impl, | ||||
| /// The output this view is currently associated with | ||||
| output: *Output, | ||||
|  | ||||
| /// This is non-null exactly when the view is mapped | ||||
| surface: ?*wlr.Surface = null, | ||||
| tree: *wlr.SceneTree, | ||||
|  | ||||
| /// This indicates that the view should be destroyed when the current | ||||
| /// transaction completes. See View.destroy() | ||||
| @ -116,7 +113,7 @@ draw_borders: bool = true, | ||||
| request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = | ||||
|     wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), | ||||
|  | ||||
| pub fn init(self: *Self, output: *Output, impl: Impl) void { | ||||
| pub fn init(self: *Self, output: *Output, tree: *wlr.SceneTree, impl: Impl) void { | ||||
|     const initial_tags = blk: { | ||||
|         const tags = output.current.tags & server.config.spawn_tagmask; | ||||
|         break :blk if (tags != 0) tags else output.current.tags; | ||||
| @ -125,6 +122,7 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void { | ||||
|     self.* = .{ | ||||
|         .impl = impl, | ||||
|         .output = output, | ||||
|         .tree = tree, | ||||
|         .current = .{ .tags = initial_tags }, | ||||
|         .pending = .{ .tags = initial_tags }, | ||||
|     }; | ||||
| @ -134,7 +132,6 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void { | ||||
| /// mark this view for destruction when the transaction completes. Otherwise | ||||
| /// destroy immediately. | ||||
| pub fn destroy(self: *Self) void { | ||||
|     assert(self.surface == null); | ||||
|     self.destroying = true; | ||||
|  | ||||
|     // If there are still saved buffers, then this view needs to be kept | ||||
| @ -210,10 +207,19 @@ fn lastSetFullscreenState(self: Self) bool { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub fn rootSurface(self: Self) *wlr.Surface { | ||||
|     assert(!self.destroying); | ||||
|     return switch (self.impl) { | ||||
|         .xdg_toplevel => |xdg_toplevel| xdg_toplevel.rootSurface(), | ||||
|         .xwayland_view => |xwayland_view| xwayland_view.rootSurface(), | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub fn sendFrameDone(self: Self) void { | ||||
|     assert(!self.destroying); | ||||
|     var now: os.timespec = undefined; | ||||
|     os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported"); | ||||
|     self.surface.?.sendFrameDone(&now); | ||||
|     self.rootSurface().sendFrameDone(&now); | ||||
| } | ||||
|  | ||||
| pub fn dropSavedBuffers(self: *Self) void { | ||||
| @ -267,12 +273,6 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void { | ||||
|         destination_output.sendUrgentTags(); | ||||
|     } | ||||
|  | ||||
|     // if the view is mapped send enter/leave events | ||||
|     if (self.surface != null) { | ||||
|         self.sendLeave(self.output); | ||||
|         self.sendEnter(destination_output); | ||||
|     } | ||||
|  | ||||
|     self.output = destination_output; | ||||
|  | ||||
|     var output_width: i32 = undefined; | ||||
| @ -365,9 +365,8 @@ pub inline fn forEachSurface( | ||||
|         .xdg_toplevel => |xdg_toplevel| { | ||||
|             xdg_toplevel.xdg_toplevel.base.forEachSurface(T, iterator, user_data); | ||||
|         }, | ||||
|         .xwayland_view => { | ||||
|             assert(build_options.xwayland); | ||||
|             self.surface.?.forEachSurface(T, iterator, user_data); | ||||
|         .xwayland_view => |xwayland_view| { | ||||
|             xwayland_view.xwayland_surface.surface.?.forEachSurface(T, iterator, user_data); | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| @ -489,9 +488,6 @@ pub fn unmap(self: *Self) void { | ||||
|  | ||||
|     if (self.saved_buffers.items.len == 0) self.saveBuffers(); | ||||
|  | ||||
|     assert(self.surface != null); | ||||
|     self.surface = null; | ||||
|  | ||||
|     // Inform all seats that the view has been unmapped so they can handle focus | ||||
|     var it = server.input_manager.seats.first; | ||||
|     while (it) |seat_node| : (it = seat_node.next) seat_node.data.handleViewUnmap(self); | ||||
|  | ||||
| @ -62,7 +62,9 @@ pub fn create(output: *Output, xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory | ||||
|     const node = try util.gpa.create(ViewStack(View).Node); | ||||
|     const view = &node.view; | ||||
|  | ||||
|     view.init(output, .{ .xdg_toplevel = .{ | ||||
|     const tree = try output.tree.createSceneXdgSurface(xdg_toplevel.base); | ||||
|  | ||||
|     view.init(output, tree, .{ .xdg_toplevel = .{ | ||||
|         .view = view, | ||||
|         .xdg_toplevel = xdg_toplevel, | ||||
|     } }); | ||||
| @ -99,6 +101,10 @@ pub fn lastSetFullscreenState(self: Self) bool { | ||||
|     return self.xdg_toplevel.scheduled.fullscreen; | ||||
| } | ||||
|  | ||||
| pub fn rootSurface(self: Self) *wlr.Surface { | ||||
|     return self.xdg_toplevel.base.surface; | ||||
| } | ||||
|  | ||||
| /// Close the view. This will lead to the unmap and destroy events being sent | ||||
| pub fn close(self: Self) void { | ||||
|     self.xdg_toplevel.sendClose(); | ||||
| @ -190,9 +196,6 @@ fn handleMap(listener: *wl.Listener(void)) void { | ||||
|     self.xdg_toplevel.scheduled.width = initial_box.width; | ||||
|     self.xdg_toplevel.scheduled.height = initial_box.height; | ||||
|  | ||||
|     view.surface = self.xdg_toplevel.base.surface; | ||||
|     view.surface_box = initial_box; | ||||
|  | ||||
|     // Also use the view's  "natural" size as the initial regular dimensions, | ||||
|     // for the case that it does not get arranged by a lyaout. | ||||
|     view.pending.box = view.float_box; | ||||
| @ -275,7 +278,10 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { | ||||
|                 const self_tags_changed = view.pending.tags != view.current.tags; | ||||
|                 const urgent_tags_dirty = view.pending.urgent != view.current.urgent or | ||||
|                     (view.pending.urgent and self_tags_changed); | ||||
|  | ||||
|                 view.current = view.pending; | ||||
|                 view.tree.node.setPosition(view.current.box.x, view.current.box.y); | ||||
|  | ||||
|                 if (self_tags_changed) view.output.sendViewTags(); | ||||
|                 if (urgent_tags_dirty) view.output.sendUrgentTags(); | ||||
|  | ||||
|  | ||||
| @ -134,7 +134,7 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSur | ||||
|                 focused.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and | ||||
|                 seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface) | ||||
|             { | ||||
|                 seat.keyboardEnterOrLeave(focused.surface.?); | ||||
|                 seat.keyboardEnterOrLeave(focused.rootSurface()); | ||||
|             }, | ||||
|             .xwayland_override_redirect => |focused| if (focused == self) seat.focus(null), | ||||
|             .layer, .lock_surface, .none => {}, | ||||
|  | ||||
| @ -67,7 +67,10 @@ pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{Out | ||||
|     const node = try util.gpa.create(ViewStack(View).Node); | ||||
|     const view = &node.view; | ||||
|  | ||||
|     view.init(output, .{ .xwayland_view = .{ | ||||
|     // TODO actually render xwayland windows, not just an empty tree. | ||||
|     const tree = try output.tree.createSceneTree(); | ||||
|  | ||||
|     view.init(output, tree, .{ .xwayland_view = .{ | ||||
|         .view = view, | ||||
|         .xwayland_surface = xwayland_surface, | ||||
|     } }); | ||||
| @ -115,6 +118,11 @@ pub fn lastSetFullscreenState(self: Self) bool { | ||||
|     return self.last_set_fullscreen_state; | ||||
| } | ||||
|  | ||||
| pub fn rootSurface(self: Self) *wlr.Surface { | ||||
|     // TODO This is probably not OK, understand when xwayland surfaces can be null. | ||||
|     return self.xwayland_surface.surface.?; | ||||
| } | ||||
|  | ||||
| /// Close the view. This will lead to the unmap and destroy events being sent | ||||
| pub fn close(self: Self) void { | ||||
|     self.xwayland_surface.close(); | ||||
| @ -198,14 +206,6 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: | ||||
|     xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen); | ||||
|     xwayland_surface.events.request_minimize.add(&self.request_minimize); | ||||
|  | ||||
|     view.surface = surface; | ||||
|     self.view.surface_box = .{ | ||||
|         .x = 0, | ||||
|         .y = 0, | ||||
|         .width = surface.current.width, | ||||
|         .height = surface.current.height, | ||||
|     }; | ||||
|  | ||||
|     // Use the view's "natural" size centered on the output as the default | ||||
|     // floating dimensions | ||||
|     view.float_box = .{ | ||||
| @ -257,7 +257,7 @@ fn handleRequestConfigure( | ||||
|     const self = @fieldParentPtr(Self, "request_configure", listener); | ||||
|  | ||||
|     // If unmapped, let the client do whatever it wants | ||||
|     if (self.view.surface == null) { | ||||
|     if (!self.xwayland_surface.mapped) { | ||||
|         self.xwayland_surface.configure(event.x, event.y, event.width, event.height); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @ -70,5 +70,5 @@ pub fn focusView( | ||||
| } | ||||
|  | ||||
| fn filter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and view.pending.tags & filter_tags != 0; | ||||
|     return view.tree.node.enabled and view.pending.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
| @ -78,6 +78,6 @@ pub fn swap( | ||||
| } | ||||
|  | ||||
| fn filter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and !view.pending.float and | ||||
|     return view.tree.node.enabled and !view.pending.float and | ||||
|         !view.pending.fullscreen and view.pending.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
| @ -59,6 +59,6 @@ pub fn zoom( | ||||
| } | ||||
|  | ||||
| fn filter(view: *View, filter_tags: u32) bool { | ||||
|     return view.surface != null and !view.pending.float and | ||||
|     return view.tree.node.enabled and !view.pending.float and | ||||
|         !view.pending.fullscreen and view.pending.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
							
								
								
									
										401
									
								
								river/render.zig
									
									
									
									
									
								
							
							
						
						
									
										401
									
								
								river/render.zig
									
									
									
									
									
								
							| @ -14,411 +14,22 @@ | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const build_options = @import("build_options"); | ||||
| const std = @import("std"); | ||||
| const os = std.os; | ||||
| const wlr = @import("wlroots"); | ||||
| const wl = @import("wayland").server.wl; | ||||
| const pixman = @import("pixman"); | ||||
|  | ||||
| const server = &@import("main.zig").server; | ||||
| const util = @import("util.zig"); | ||||
|  | ||||
| const LayerSurface = @import("LayerSurface.zig"); | ||||
| const Output = @import("Output.zig"); | ||||
| const Server = @import("Server.zig"); | ||||
| const View = @import("View.zig"); | ||||
| const ViewStack = @import("view_stack.zig").ViewStack; | ||||
|  | ||||
| const log = std.log.scoped(.render); | ||||
|  | ||||
| const SurfaceRenderData = struct { | ||||
|     output: *const Output, | ||||
|  | ||||
|     /// In output layout coordinates relative to the output | ||||
|     output_x: i32, | ||||
|     output_y: i32, | ||||
|  | ||||
|     when: *os.timespec, | ||||
| }; | ||||
|  | ||||
| /// The rendering order in this function must be kept in sync with Cursor.surfaceAt() | ||||
| pub fn renderOutput(output: *Output) void { | ||||
|     const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?; | ||||
|     if (!scene_output.commit()) { | ||||
|         log.err("output commit failed for {s}", .{output.wlr_output.name}); | ||||
|     } | ||||
|  | ||||
|     var now: os.timespec = undefined; | ||||
|     os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported"); | ||||
|  | ||||
|     output.wlr_output.attachRender(null) catch { | ||||
|         log.err("failed to attach renderer", .{}); | ||||
|         return; | ||||
|     }; | ||||
|  | ||||
|     server.renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height)); | ||||
|  | ||||
|     // In order to avoid flashing a blank black screen as the session is locked | ||||
|     // continue to render the unlocked session until either a lock surface is | ||||
|     // created or waiting for lock surfaces times out. | ||||
|     if (server.lock_manager.state == .locked or | ||||
|         (server.lock_manager.state == .waiting_for_lock_surfaces and output.lock_surface != null) or | ||||
|         server.lock_manager.state == .waiting_for_blank) | ||||
|     { | ||||
|         server.renderer.clear(&[_]f32{ 0, 0, 0, 1 }); // solid black | ||||
|  | ||||
|         // TODO: this isn't frame-perfect if the output mode is changed. We | ||||
|         // could possibly delay rendering new frames after the mode change | ||||
|         // until the surface commits a buffer of the correct size. | ||||
|         if (output.lock_surface) |lock_surface| { | ||||
|             var rdata = SurfaceRenderData{ | ||||
|                 .output = output, | ||||
|                 .output_x = 0, | ||||
|                 .output_y = 0, | ||||
|                 .when = &now, | ||||
|             }; | ||||
|             lock_surface.wlr_lock_surface.surface.forEachSurface( | ||||
|                 *SurfaceRenderData, | ||||
|                 renderSurfaceIterator, | ||||
|                 &rdata, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         renderDragIcons(output, &now); | ||||
|  | ||||
|         output.wlr_output.renderSoftwareCursors(null); | ||||
|         server.renderer.end(); | ||||
|         output.wlr_output.commit() catch { | ||||
|             log.err("output commit failed for {s}", .{output.wlr_output.name}); | ||||
|             return; | ||||
|         }; | ||||
|  | ||||
|         if (server.lock_manager.state == .locked) { | ||||
|             switch (output.lock_render_state) { | ||||
|                 .unlocked, .pending_blank, .pending_lock_surface => unreachable, | ||||
|                 .blanked, .lock_surface => {}, | ||||
|             } | ||||
|         } else { | ||||
|             if (output.lock_surface == null) { | ||||
|                 output.lock_render_state = .pending_blank; | ||||
|             } else { | ||||
|                 output.lock_render_state = .pending_lock_surface; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|     output.lock_render_state = .unlocked; | ||||
|  | ||||
|     // Find the first visible fullscreen view in the stack if there is one | ||||
|     var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter); | ||||
|     const fullscreen_view = while (it.next()) |view| { | ||||
|         if (view.current.fullscreen) break view; | ||||
|     } else null; | ||||
|  | ||||
|     // If we have a fullscreen view to render, render it. | ||||
|     if (fullscreen_view) |view| { | ||||
|         // Always clear with solid black for fullscreen | ||||
|         server.renderer.clear(&[_]f32{ 0, 0, 0, 1 }); | ||||
|         renderView(output, view, &now); | ||||
|         if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now); | ||||
|     } else { | ||||
|         // No fullscreen view, so render normal layers/views | ||||
|         server.renderer.clear(&server.config.background_color); | ||||
|  | ||||
|         renderLayer(output, output.getLayer(.background).*, &now, .toplevels); | ||||
|         renderLayer(output, output.getLayer(.bottom).*, &now, .toplevels); | ||||
|  | ||||
|         // The first view in the list is "on top" so always iterate in reverse. | ||||
|  | ||||
|         // non-focused, non-floating views | ||||
|         it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); | ||||
|         while (it.next()) |view| { | ||||
|             if (view.current.focus != 0 or view.current.float) continue; | ||||
|             if (view.draw_borders) renderBorders(output, view); | ||||
|             renderView(output, view, &now); | ||||
|         } | ||||
|  | ||||
|         // focused, non-floating views | ||||
|         it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); | ||||
|         while (it.next()) |view| { | ||||
|             if (view.current.focus == 0 or view.current.float) continue; | ||||
|             if (view.draw_borders) renderBorders(output, view); | ||||
|             renderView(output, view, &now); | ||||
|         } | ||||
|  | ||||
|         // non-focused, floating views | ||||
|         it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); | ||||
|         while (it.next()) |view| { | ||||
|             if (view.current.focus != 0 or !view.current.float) continue; | ||||
|             if (view.draw_borders) renderBorders(output, view); | ||||
|             renderView(output, view, &now); | ||||
|         } | ||||
|  | ||||
|         // focused, floating views | ||||
|         it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter); | ||||
|         while (it.next()) |view| { | ||||
|             if (view.current.focus == 0 or !view.current.float) continue; | ||||
|             if (view.draw_borders) renderBorders(output, view); | ||||
|             renderView(output, view, &now); | ||||
|         } | ||||
|  | ||||
|         if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now); | ||||
|  | ||||
|         renderLayer(output, output.getLayer(.top).*, &now, .toplevels); | ||||
|  | ||||
|         renderLayer(output, output.getLayer(.background).*, &now, .popups); | ||||
|         renderLayer(output, output.getLayer(.bottom).*, &now, .popups); | ||||
|         renderLayer(output, output.getLayer(.top).*, &now, .popups); | ||||
|     } | ||||
|  | ||||
|     // The overlay layer is rendered in both fullscreen and normal cases | ||||
|     renderLayer(output, output.getLayer(.overlay).*, &now, .toplevels); | ||||
|     renderLayer(output, output.getLayer(.overlay).*, &now, .popups); | ||||
|  | ||||
|     renderDragIcons(output, &now); | ||||
|  | ||||
|     output.wlr_output.renderSoftwareCursors(null); | ||||
|  | ||||
|     server.renderer.end(); | ||||
|  | ||||
|     output.wlr_output.commit() catch | ||||
|         log.err("output commit failed for {s}", .{output.wlr_output.name}); | ||||
| } | ||||
|  | ||||
| fn renderFilter(view: *View, filter_tags: u32) bool { | ||||
|     // This check prevents a race condition when a frame is requested | ||||
|     // between mapping of a view and the first configure being handled. | ||||
|     if (view.current.box.width == 0 or view.current.box.height == 0) | ||||
|         return false; | ||||
|     return view.current.tags & filter_tags != 0; | ||||
| } | ||||
|  | ||||
| /// Render all surfaces on the passed layer | ||||
| fn renderLayer( | ||||
|     output: *const Output, | ||||
|     layer: std.TailQueue(LayerSurface), | ||||
|     now: *os.timespec, | ||||
|     role: enum { toplevels, popups }, | ||||
| ) void { | ||||
|     var it = layer.first; | ||||
|     while (it) |node| : (it = node.next) { | ||||
|         const layer_surface = &node.data; | ||||
|         var rdata = SurfaceRenderData{ | ||||
|             .output = output, | ||||
|             .output_x = layer_surface.box.x, | ||||
|             .output_y = layer_surface.box.y, | ||||
|             .when = now, | ||||
|         }; | ||||
|         switch (role) { | ||||
|             .toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface( | ||||
|                 *SurfaceRenderData, | ||||
|                 renderSurfaceIterator, | ||||
|                 &rdata, | ||||
|             ), | ||||
|             .popups => layer_surface.wlr_layer_surface.forEachPopupSurface( | ||||
|                 *SurfaceRenderData, | ||||
|                 renderSurfaceIterator, | ||||
|                 &rdata, | ||||
|             ), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Render all surfaces in the view's surface tree, including subsurfaces and popups | ||||
| fn renderView(output: *const Output, view: *View, now: *os.timespec) void { | ||||
|     // If we have saved buffers, we are in the middle of a transaction | ||||
|     // and need to render those buffers until the transaction is complete. | ||||
|     if (view.saved_buffers.items.len != 0) { | ||||
|         for (view.saved_buffers.items) |saved_buffer| { | ||||
|             const texture = saved_buffer.client_buffer.texture orelse continue; | ||||
|             renderTexture( | ||||
|                 output, | ||||
|                 texture, | ||||
|                 .{ | ||||
|                     .x = saved_buffer.surface_box.x + view.current.box.x - view.saved_surface_box.x, | ||||
|                     .y = saved_buffer.surface_box.y + view.current.box.y - view.saved_surface_box.y, | ||||
|                     .width = saved_buffer.surface_box.width, | ||||
|                     .height = saved_buffer.surface_box.height, | ||||
|                 }, | ||||
|                 &saved_buffer.source_box, | ||||
|                 saved_buffer.transform, | ||||
|             ); | ||||
|         } | ||||
|     } else { | ||||
|         // Since there are no stashed buffers, we are not in the middle of | ||||
|         // a transaction and may simply render the most recent buffers provided | ||||
|         // by the client. | ||||
|         var rdata = SurfaceRenderData{ | ||||
|             .output = output, | ||||
|             .output_x = view.current.box.x - view.surface_box.x, | ||||
|             .output_y = view.current.box.y - view.surface_box.y, | ||||
|             .when = now, | ||||
|         }; | ||||
|         view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn renderDragIcons(output: *const Output, now: *os.timespec) void { | ||||
|     var output_box: wlr.Box = undefined; | ||||
|     server.root.output_layout.getBox(output.wlr_output, &output_box); | ||||
|  | ||||
|     var it = server.input_manager.seats.first; | ||||
|     while (it) |node| : (it = node.next) { | ||||
|         const icon = node.data.drag_icon orelse continue; | ||||
|  | ||||
|         var lx: f64 = undefined; | ||||
|         var ly: f64 = undefined; | ||||
|         switch (icon.wlr_drag_icon.drag.grab_type) { | ||||
|             .keyboard_pointer => { | ||||
|                 lx = icon.seat.cursor.wlr_cursor.x; | ||||
|                 ly = icon.seat.cursor.wlr_cursor.y; | ||||
|             }, | ||||
|             .keyboard_touch => { | ||||
|                 const touch_id = icon.wlr_drag_icon.drag.touch_id; | ||||
|                 const point = icon.seat.cursor.touch_points.get(touch_id) orelse continue; | ||||
|                 lx = point.lx; | ||||
|                 ly = point.ly; | ||||
|             }, | ||||
|             .keyboard => unreachable, | ||||
|         } | ||||
|  | ||||
|         var rdata = SurfaceRenderData{ | ||||
|             .output = output, | ||||
|             .output_x = @floatToInt(i32, lx) + icon.sx - output_box.x, | ||||
|             .output_y = @floatToInt(i32, ly) + icon.sy - output_box.y, | ||||
|             .when = now, | ||||
|         }; | ||||
|         icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Render all override redirect xwayland windows that appear on the output | ||||
| fn renderXwaylandOverrideRedirect(output: *const Output, now: *os.timespec) void { | ||||
|     var output_box: wlr.Box = undefined; | ||||
|     server.root.output_layout.getBox(output.wlr_output, &output_box); | ||||
|  | ||||
|     var it = server.root.xwayland_override_redirect_views.last; | ||||
|     while (it) |node| : (it = node.prev) { | ||||
|         const xwayland_surface = node.data.xwayland_surface; | ||||
|  | ||||
|         var rdata = SurfaceRenderData{ | ||||
|             .output = output, | ||||
|             .output_x = xwayland_surface.x - output_box.x, | ||||
|             .output_y = xwayland_surface.y - output_box.y, | ||||
|             .when = now, | ||||
|         }; | ||||
|         xwayland_surface.surface.?.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// This function is passed to wlroots to render each surface during iteration | ||||
| fn renderSurfaceIterator( | ||||
|     surface: *wlr.Surface, | ||||
|     surface_x: c_int, | ||||
|     surface_y: c_int, | ||||
|     rdata: *SurfaceRenderData, | ||||
| ) void { | ||||
|     const texture = surface.getTexture() orelse return; | ||||
|  | ||||
|     var source_box: wlr.FBox = undefined; | ||||
|     surface.getBufferSourceBox(&source_box); | ||||
|  | ||||
|     renderTexture( | ||||
|         rdata.output, | ||||
|         texture, | ||||
|         .{ | ||||
|             .x = rdata.output_x + surface_x, | ||||
|             .y = rdata.output_y + surface_y, | ||||
|             .width = surface.current.width, | ||||
|             .height = surface.current.height, | ||||
|         }, | ||||
|         &source_box, | ||||
|         surface.current.transform, | ||||
|     ); | ||||
|  | ||||
|     surface.sendFrameDone(rdata.when); | ||||
| } | ||||
|  | ||||
| /// Render the given texture at the given box, taking the scale and transform | ||||
| /// of the output into account. | ||||
| fn renderTexture( | ||||
|     output: *const Output, | ||||
|     texture: *wlr.Texture, | ||||
|     dest_box: wlr.Box, | ||||
|     source_box: *const wlr.FBox, | ||||
|     transform: wl.Output.Transform, | ||||
| ) void { | ||||
|     var box = dest_box; | ||||
|  | ||||
|     // Scale the box to the output's current scaling factor | ||||
|     scaleBox(&box, output.wlr_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. | ||||
|     var matrix: [9]f32 = undefined; | ||||
|     const inverted = wlr.Output.transformInvert(transform); | ||||
|     wlr.matrix.projectBox(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix); | ||||
|  | ||||
|     // This takes our matrix, the texture, and an alpha, and performs the actual | ||||
|     // rendering on the GPU. | ||||
|     server.renderer.renderSubtextureWithMatrix(texture, source_box, &matrix, 1.0) catch return; | ||||
| } | ||||
|  | ||||
| fn renderBorders(output: *const Output, view: *View) void { | ||||
|     const config = &server.config; | ||||
|     const color = blk: { | ||||
|         if (view.current.urgent) break :blk &config.border_color_urgent; | ||||
|         if (view.current.focus != 0) break :blk &config.border_color_focused; | ||||
|         break :blk &config.border_color_unfocused; | ||||
|     }; | ||||
|     const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box; | ||||
|  | ||||
|     var border: wlr.Box = undefined; | ||||
|  | ||||
|     // left and right, covering the corners as well | ||||
|     border.y = view.current.box.y - config.border_width; | ||||
|     border.width = config.border_width; | ||||
|     border.height = actual_box.height + config.border_width * 2; | ||||
|  | ||||
|     // left | ||||
|     border.x = view.current.box.x - config.border_width; | ||||
|     renderRect(output, border, color); | ||||
|  | ||||
|     // right | ||||
|     border.x = view.current.box.x + actual_box.width; | ||||
|     renderRect(output, border, color); | ||||
|  | ||||
|     // top and bottom | ||||
|     border.x = view.current.box.x; | ||||
|     border.width = actual_box.width; | ||||
|     border.height = config.border_width; | ||||
|  | ||||
|     // top | ||||
|     border.y = view.current.box.y - config.border_width; | ||||
|     renderRect(output, border, color); | ||||
|  | ||||
|     // bottom border | ||||
|     border.y = view.current.box.y + actual_box.height; | ||||
|     renderRect(output, border, color); | ||||
| } | ||||
|  | ||||
| fn renderRect(output: *const Output, box: wlr.Box, color: *const [4]f32) void { | ||||
|     var scaled = box; | ||||
|     scaleBox(&scaled, output.wlr_output.scale); | ||||
|     server.renderer.renderRect(&scaled, color, &output.wlr_output.transform_matrix); | ||||
| } | ||||
|  | ||||
| /// Scale a wlr_box, taking the possibility of fractional scaling into account. | ||||
| fn scaleBox(box: *wlr.Box, scale: f64) void { | ||||
|     box.width = scaleLength(box.width, box.x, scale); | ||||
|     box.height = scaleLength(box.height, box.y, scale); | ||||
|     box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale)); | ||||
|     box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * 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)); | ||||
|     scene_output.sendFrameDone(&now); | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user