render: do basic yes/no damage tracking

This commit is contained in:
Isaac Freund 2021-05-23 17:10:26 +02:00
parent 3390f223a8
commit 13f01bcb4b
11 changed files with 233 additions and 54 deletions

2
deps/zig-pixman vendored

@ -1 +1 @@
Subproject commit 9acac698e073ff54b09a62fecb144de326f67626
Subproject commit 135f22345671e0ae2d1bc4b27cfdee9e97b97dfc

View file

@ -26,6 +26,7 @@ const util = @import("util.zig");
const Box = @import("Box.zig");
const Output = @import("Output.zig");
const Subsurface = @import("Subsurface.zig");
const XdgPopup = @import("XdgPopup.zig");
const log = std.log.scoped(.layer_shell);
@ -40,10 +41,11 @@ state: wlr.LayerSurfaceV1.State,
destroy: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleDestroy),
map: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleMap),
unmap: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleUnmap),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface),
// Listeners only active while the layer surface is mapped
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
pub fn init(self: *Self, output: *Output, wlr_layer_surface: *wlr.LayerSurfaceV1) void {
self.* = .{
@ -62,9 +64,11 @@ pub fn init(self: *Self, output: *Output, wlr_layer_surface: *wlr.LayerSurfaceV1
list.remove(node);
// Set up listeners that are active for the entire lifetime of the layer surface
self.wlr_layer_surface.events.destroy.add(&self.destroy);
self.wlr_layer_surface.events.map.add(&self.map);
self.wlr_layer_surface.events.unmap.add(&self.unmap);
wlr_layer_surface.events.destroy.add(&self.destroy);
wlr_layer_surface.events.map.add(&self.map);
wlr_layer_surface.events.unmap.add(&self.unmap);
wlr_layer_surface.events.new_popup.add(&self.new_popup);
wlr_layer_surface.surface.events.new_subsurface.add(&self.new_subsurface);
}
fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
@ -76,6 +80,8 @@ fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface:
self.destroy.link.remove();
self.map.link.remove();
self.unmap.link.remove();
self.new_popup.link.remove();
self.new_subsurface.link.remove();
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
util.gpa.destroy(node);
@ -88,7 +94,6 @@ fn handleMap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wl
// Add listeners that are only active while mapped
wlr_layer_surface.surface.events.commit.add(&self.commit);
wlr_layer_surface.events.new_popup.add(&self.new_popup);
wlr_layer_surface.surface.sendEnter(wlr_layer_surface.output.?);
@ -103,7 +108,6 @@ fn handleUnmap(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *
// remove listeners only active while the layer surface is mapped
self.commit.link.remove();
self.new_popup.link.remove();
// Remove from the output's list of layer surfaces
const self_node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
@ -154,15 +158,16 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), wlr_surface: *wlr.Surface)
self.output.arrangeLayers();
server.root.startTransaction();
}
self.output.damage.addWhole();
}
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "new_popup", listener);
// This will free itself on destroy
const xdg_popup = util.gpa.create(XdgPopup) catch {
wlr_xdg_popup.resource.postNoMemory();
return;
};
xdg_popup.init(self.output, &self.box, wlr_xdg_popup);
XdgPopup.create(wlr_xdg_popup, .{ .layer_surface = self });
}
fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void {
const self = @fieldParentPtr(Self, "new_subsurface", listener);
Subsurface.create(new_wlr_subsurface, .{ .layer_surface = self });
}

View file

@ -55,6 +55,7 @@ const State = struct {
};
wlr_output: *wlr.Output,
damage: *wlr.OutputDamage,
/// All layer surfaces on the output, indexed by the layer enum.
layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} ** 4,
@ -93,8 +94,8 @@ status_trackers: std.SinglyLinkedList(OutputStatus) = .{},
destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy),
enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
frame: wl.Listener(*wlr.OutputDamage) = wl.Listener(*wlr.OutputDamage).init(handleFrame),
pub fn init(self: *Self, wlr_output: *wlr.Output) !void {
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
@ -110,15 +111,17 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void {
self.* = .{
.wlr_output = wlr_output,
.damage = try wlr.OutputDamage.create(wlr_output),
.usable_box = undefined,
};
wlr_output.data = @ptrToInt(self);
wlr_output.events.destroy.add(&self.destroy);
wlr_output.events.enable.add(&self.enable);
wlr_output.events.frame.add(&self.frame);
wlr_output.events.mode.add(&self.mode);
self.damage.events.frame.add(&self.frame);
if (wlr_output.isNoop()) {
// A noop output is always 0 x 0
self.usable_box = .{
@ -453,7 +456,7 @@ fn handleEnable(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) vo
if (wlr_output.enabled) server.root.addOutput(self);
}
fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
fn handleFrame(listener: *wl.Listener(*wlr.OutputDamage), wlr_output: *wlr.OutputDamage) void {
// This function is called every time an output is ready to display a frame,
// generally at the output's refresh rate (e.g. 60Hz).
const self = @fieldParentPtr(Self, "frame", listener);

View file

@ -404,6 +404,8 @@ fn commitTransaction(self: *Self) void {
}
if (view_tags_changed) output.sendViewTags();
output.damage.addWhole();
}
}

103
river/Subsurface.zig Normal file
View file

@ -0,0 +1,103 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2021 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// 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 Subsurface = @This();
const std = @import("std");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const util = @import("util.zig");
const LayerSurface = @import("LayerSurface.zig");
const View = @import("View.zig");
pub const Parent = union(enum) {
view: *View,
layer_surface: *LayerSurface,
pub fn damageWholeOutput(parent: Parent) void {
switch (parent) {
.view => |view| view.output.damage.addWhole(),
.layer_surface => |layer_surface| layer_surface.output.damage.addWhole(),
}
}
};
/// The parent at the root of this surface tree
parent: Parent,
wlr_subsurface: *wlr.Subsurface,
// Always active
destroy: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleDestroy),
map: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleMap),
unmap: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleUnmap),
new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface),
// Only active while mapped
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
pub fn create(wlr_subsurface: *wlr.Subsurface, parent: Parent) void {
const subsurface = util.gpa.create(Subsurface) catch {
std.log.crit("out of memory", .{});
wlr_subsurface.resource.getClient().postNoMemory();
return;
};
subsurface.* = .{ .wlr_subsurface = wlr_subsurface, .parent = parent };
wlr_subsurface.events.destroy.add(&subsurface.destroy);
wlr_subsurface.events.map.add(&subsurface.map);
wlr_subsurface.events.unmap.add(&subsurface.unmap);
wlr_subsurface.surface.events.new_subsurface.add(&subsurface.new_subsurface);
}
fn handleDestroy(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void {
const subsurface = @fieldParentPtr(Subsurface, "destroy", listener);
subsurface.destroy.link.remove();
subsurface.map.link.remove();
subsurface.unmap.link.remove();
subsurface.new_subsurface.link.remove();
util.gpa.destroy(subsurface);
}
fn handleMap(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void {
const subsurface = @fieldParentPtr(Subsurface, "map", listener);
wlr_subsurface.surface.events.commit.add(&subsurface.commit);
subsurface.parent.damageWholeOutput();
}
fn handleUnmap(listener: *wl.Listener(*wlr.Subsurface), wlr_subsurface: *wlr.Subsurface) void {
const subsurface = @fieldParentPtr(Subsurface, "unmap", listener);
subsurface.commit.link.remove();
subsurface.parent.damageWholeOutput();
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const subsurface = @fieldParentPtr(Subsurface, "commit", listener);
subsurface.parent.damageWholeOutput();
}
fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void {
const subsurface = @fieldParentPtr(Subsurface, "new_subsurface", listener);
Subsurface.create(new_wlr_subsurface, subsurface.parent);
}

View file

@ -278,13 +278,14 @@ pub fn saveBuffers(self: *Self) void {
/// Otherwise, apply the pending state immediately.
pub fn notifyConfiguredOrApplyPending(self: *Self) void {
self.pending_serial = null;
if (self.shouldTrackConfigure())
server.root.notifyConfigured()
else {
if (self.shouldTrackConfigure()) {
server.root.notifyConfigured();
} else {
const self_tags_changed = self.pending.tags != self.current.tags;
self.current = self.pending;
self.commitOpacityTransition();
if (self_tags_changed) self.output.sendViewTags();
self.output.damage.addWhole();
}
}

View file

@ -23,32 +23,44 @@ const wl = @import("wayland").server.wl;
const util = @import("util.zig");
const Box = @import("Box.zig");
const Output = @import("Output.zig");
const Subsurface = @import("Subsurface.zig");
const Parent = Subsurface.Parent;
const log = std.log.scoped(.server);
/// The output this popup is displayed on.
output: *Output,
/// Box of the parent of this popup tree. Needed to unconstrain child popups.
parent_box: *const Box,
/// The corresponding wlroots object
/// The parent at the root of this surface tree
parent: Parent,
wlr_xdg_popup: *wlr.XdgPopup,
// Always active
destroy: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleDestroy),
map: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleMap),
unmap: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleUnmap),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface),
pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup: *wlr.XdgPopup) void {
// Only active while mapped
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
pub fn create(wlr_xdg_popup: *wlr.XdgPopup, parent: Parent) void {
const self = util.gpa.create(Self) catch {
std.log.crit("out of memory", .{});
wlr_xdg_popup.resource.postNoMemory();
return;
};
self.* = .{
.output = output,
.parent_box = parent_box,
.parent = parent,
.wlr_xdg_popup = wlr_xdg_popup,
};
const parent_box = switch (parent) {
.view => |view| &view.pending.box,
.layer_surface => |layer_surface| &layer_surface.box,
};
const output_dimensions = switch (parent) {
.view => |view| view.output.getEffectiveResolution(),
.layer_surface => |layer_surface| layer_surface.output.getEffectiveResolution(),
};
// The output box relative to the parent of the popup
const output_dimensions = output.getEffectiveResolution();
var box = wlr.Box{
.x = -parent_box.x,
.y = -parent_box.y,
@ -58,27 +70,52 @@ pub fn init(self: *Self, output: *Output, parent_box: *const Box, wlr_xdg_popup:
wlr_xdg_popup.unconstrainFromBox(&box);
wlr_xdg_popup.base.events.destroy.add(&self.destroy);
wlr_xdg_popup.base.events.map.add(&self.map);
wlr_xdg_popup.base.events.unmap.add(&self.unmap);
wlr_xdg_popup.base.events.new_popup.add(&self.new_popup);
wlr_xdg_popup.base.surface.events.new_subsurface.add(&self.new_subsurface);
}
fn handleDestroy(listener: *wl.Listener(*wlr.XdgSurface), wlr_xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "destroy", listener);
self.destroy.link.remove();
self.map.link.remove();
self.unmap.link.remove();
self.new_popup.link.remove();
self.new_subsurface.link.remove();
util.gpa.destroy(self);
}
/// Called when a new xdg popup is requested by the client
fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "map", listener);
self.wlr_xdg_popup.base.surface.events.commit.add(&self.commit);
self.parent.damageWholeOutput();
}
fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "unmap", listener);
self.commit.link.remove();
self.parent.damageWholeOutput();
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "commit", listener);
self.parent.damageWholeOutput();
}
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "new_popup", listener);
// This will free itself on destroy
const xdg_popup = util.gpa.create(Self) catch {
wlr_xdg_popup.resource.postNoMemory();
log.crit("out of memory", .{});
return;
};
xdg_popup.init(self.output, self.parent_box, wlr_xdg_popup);
Self.create(wlr_xdg_popup, self.parent);
}
fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void {
const self = @fieldParentPtr(Self, "new_subsurface", listener);
Subsurface.create(new_wlr_subsurface, self.parent);
}

View file

@ -26,6 +26,7 @@ const util = @import("util.zig");
const Box = @import("Box.zig");
const Seat = @import("Seat.zig");
const Subsurface = @import("Subsurface.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const XdgPopup = @import("XdgPopup.zig");
@ -42,10 +43,11 @@ xdg_surface: *wlr.XdgSurface,
destroy: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleDestroy),
map: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleMap),
unmap: wl.Listener(*wlr.XdgSurface) = wl.Listener(*wlr.XdgSurface).init(handleUnmap),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
new_subsurface: wl.Listener(*wlr.Subsurface) = wl.Listener(*wlr.Subsurface).init(handleNewSubsurface),
// Listeners that are only active while the view is mapped
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
// zig fmt: off
request_fullscreen: wl.Listener(*wlr.XdgToplevel.event.SetFullscreen) =
wl.Listener(*wlr.XdgToplevel.event.SetFullscreen).init(handleRequestFullscreen),
@ -65,6 +67,8 @@ pub fn init(self: *Self, view: *View, xdg_surface: *wlr.XdgSurface) void {
self.xdg_surface.events.destroy.add(&self.destroy);
self.xdg_surface.events.map.add(&self.map);
self.xdg_surface.events.unmap.add(&self.unmap);
self.xdg_surface.events.new_popup.add(&self.new_popup);
self.xdg_surface.surface.events.new_subsurface.add(&self.new_subsurface);
}
pub fn deinit(self: *Self) void {
@ -73,6 +77,8 @@ pub fn deinit(self: *Self) void {
self.destroy.link.remove();
self.map.link.remove();
self.unmap.link.remove();
self.new_popup.link.remove();
self.new_subsurface.link.remove();
}
}
@ -161,7 +167,6 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa
// Add listeners that are only active while mapped
self.xdg_surface.surface.events.commit.add(&self.commit);
self.xdg_surface.events.new_popup.add(&self.new_popup);
toplevel.events.request_fullscreen.add(&self.request_fullscreen);
toplevel.events.request_move.add(&self.request_move);
toplevel.events.request_resize.add(&self.request_resize);
@ -229,7 +234,6 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSur
// Remove listeners that are only active while mapped
self.commit.link.remove();
self.new_popup.link.remove();
self.request_fullscreen.link.remove();
self.request_move.link.remove();
self.request_resize.link.remove();
@ -260,6 +264,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi
view.sendFrameDone();
}
} else {
view.output.damage.addWhole();
// TODO: handle unexpected change in dimensions
if (!std.meta.eql(view.surface_box, new_box))
log.err("view changed size unexpectedly", .{});
@ -267,16 +272,14 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi
}
}
/// Called when a new xdg popup is requested by the client
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
const self = @fieldParentPtr(Self, "new_popup", listener);
XdgPopup.create(wlr_xdg_popup, .{ .view = self.view });
}
// This will free itself on destroy
const xdg_popup = util.gpa.create(XdgPopup) catch {
wlr_xdg_popup.resource.postNoMemory();
return;
};
xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup);
fn handleNewSubsurface(listener: *wl.Listener(*wlr.Subsurface), new_wlr_subsurface: *wlr.Subsurface) void {
const self = @fieldParentPtr(Self, "new_subsurface", listener);
Subsurface.create(new_wlr_subsurface, .{ .view = self.view });
}
/// Called when the client asks to be fullscreened. We always honor the request

View file

@ -37,6 +37,7 @@ request_configure: wl.Listener(*wlr.XwaylandSurface.event.Configure) =
destroy: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleDestroy),
map: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleMap),
unmap: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
pub fn init(self: *Self, xwayland_surface: *wlr.XwaylandSurface) void {
self.* = .{ .xwayland_surface = xwayland_surface };
@ -78,6 +79,8 @@ fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wl
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
server.root.xwayland_unmanaged_views.prepend(node);
xwayland_surface.surface.?.events.commit.add(&self.commit);
// TODO: handle keyboard focus
// if (wlr_xwayland_or_surface_wants_focus(self.xwayland_surface)) { ...
}
@ -89,4 +92,11 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *
// Remove self from the list of unmanged views in the root
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
server.root.xwayland_unmanaged_views.remove(node);
self.commit.link.remove();
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
var it = server.root.outputs.first;
while (it) |node| : (it = node.next) node.data.damage.addWhole();
}

View file

@ -232,6 +232,9 @@ fn handleRequestConfigure(
/// TODO: check for unexpected change in size and react as needed
fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "commit", listener);
self.view.output.damage.addWhole();
self.view.surface_box = Box{
.x = 0,
.y = 0,

View file

@ -53,7 +53,19 @@ pub fn renderOutput(output: *Output) void {
var now: os.timespec = undefined;
os.clock_gettime(os.CLOCK_MONOTONIC, &now) catch unreachable;
output.wlr_output.attachRender(null) catch return;
var needs_frame: bool = undefined;
var damage_region: pixman.Region32 = undefined;
damage_region.init();
defer damage_region.deinit();
output.damage.attachRender(&needs_frame, &damage_region) catch {
log.err("failed to attach renderer", .{});
return;
};
if (!needs_frame) {
output.wlr_output.rollback();
return;
}
renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height));