render: use wlr_scene to render views

This commit is contained in:
Isaac Freund 2023-01-27 22:09:35 +01:00
parent 07294057cb
commit 4f0ce8fceb
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
13 changed files with 78 additions and 441 deletions

View File

@ -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 {

View File

@ -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;
}

View File

@ -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", .{});

View File

@ -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}", .{

View File

@ -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,

View File

@ -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);

View File

@ -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();

View File

@ -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 => {},

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}