Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Server: recover from GPU resets
river/Server.zig | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/river/Server.zig b/river/Server.zig
index 613691c..2a1b915 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -45,6 +45,7 @@ session: ?*wlr.Session,
renderer: *wlr.Renderer,
allocator: *wlr.Allocator,
+gpu_reset_recover: ?*wl.EventSource = null,
security_context_manager: *wlr.SecurityContextManagerV1,
@@ -87,6 +88,7 @@ xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_optio
new_xsurface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void =
if (build_options.xwayland) .init(handleNewXwaylandSurface),
+renderer_lost: wl.Listener(void) = .init(handleRendererLost),
new_xdg_toplevel: wl.Listener(*wlr.XdgToplevel) = .init(handleNewXdgToplevel),
new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) = .init(handleNewToplevelDecoration),
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = .init(handleRequestActivate),
@@ -178,6 +180,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
try server.idle_inhibit_manager.init();
try server.lock_manager.init();
+ server.renderer.events.lost.add(&server.renderer_lost);
server.xdg_shell.events.new_toplevel.add(&server.new_xdg_toplevel);
server.xdg_decoration_manager.events.new_toplevel_decoration.add(&server.new_toplevel_decoration);
server.xdg_activation.events.request_activate.add(&server.request_activate);
@@ -191,6 +194,7 @@ pub fn deinit(server: *Server) void {
server.sigint_source.remove();
server.sigterm_source.remove();
+ server.renderer_lost.link.remove();
server.new_xdg_toplevel.link.remove();
server.new_toplevel_decoration.link.remove();
server.request_activate.link.remove();
@@ -341,6 +345,72 @@ fn terminate(_: c_int, wl_server: *wl.Server) c_int {
return 0;
}
+fn handleRendererLost(listener: *wl.Listener(void)) void {
+ const server: *Server = @fieldParentPtr("renderer_lost", listener);
+ if (server.gpu_reset_recover != null) {
+ log.info("ignoring GPU reset event, recovery already scheduled", .{});
+ return;
+ }
+ log.info("received GPU reset event, scheduling recovery", .{});
+ // There's a design wart in this wlroots API: calling wlr_renderer_destroy()
+ // from inside this listener for the renderer lost event causes the assertion
+ // that all listener lists are empty in wlr_renderer_destroy() to fail. This
+ // happens even if river has already called server.renderer_lost.link.remove()
+ // since wlroots uses wl_signal_emit_mutable(), which is implemented by adding
+ // temporary links to the list during iteration.
+ // Using an idle callback is the most straightforward way to work around this
+ // design wart.
+ const event_loop = server.wl_server.getEventLoop();
+ server.gpu_reset_recover = event_loop.addIdle(*Server, gpuResetRecoverIdle, server) catch |err| switch (err) {
+ error.OutOfMemory => {
+ log.err("out of memory", .{});
+ return;
+ },
+ };
+}
+
+fn gpuResetRecoverIdle(server: *Server) void {
+ server.gpu_reset_recover = null;
+ // There's not much that can be done if creating a new renderer or allocator fails.
+ // With luck there might be another GPU reset after which we try again and succeed.
+ server.gpuResetRecover() catch |err| switch (err) {
+ error.RendererCreateFailed => log.err("failed to create new renderer after GPU reset", .{}),
+ error.AllocatorCreateFailed => log.err("failed to create new allocator after GPU reset", .{}),
+ };
+}
+
+fn gpuResetRecover(server: *Server) !void {
+ log.info("recovering from GPU reset", .{});
+ const new_renderer = try wlr.Renderer.autocreate(server.backend);
+ errdefer new_renderer.destroy();
+
+ const new_allocator = try wlr.Allocator.autocreate(server.backend, new_renderer);
+ errdefer comptime unreachable; // no failure allowed after this point
+
+ server.renderer_lost.link.remove();
+ new_renderer.events.lost.add(&server.renderer_lost);
+
+ server.compositor.setRenderer(new_renderer);
+
+ {
+ var it = server.om.outputs.iterator(.forward);
+ while (it.next()) |output| {
+ if (output.wlr_output) |wlr_output| {
+ // This should never fail here as failure with this combination of
+ // renderer, allocator, and backend should have prevented creating
+ // the output in the first place.
+ _ = wlr_output.initRender(new_allocator, new_renderer);
+ }
+ }
+ }
+
+ server.renderer.destroy();
+ server.renderer = new_renderer;
+
+ server.allocator.destroy();
+ server.allocator = new_allocator;
+}
+
fn handleNewXdgToplevel(_: *wl.Listener(*wlr.XdgToplevel), xdg_toplevel: *wlr.XdgToplevel) void {
XdgToplevel.create(xdg_toplevel) catch {
log.err("out of memory", .{});