render: use wlr_scene to render views
This commit is contained in:
parent
07294057cb
commit
4f0ce8fceb
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user