1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
const std = @import("std");
const c = @import("c.zig").c;
const Server = @import("server.zig").Server;
const View = @import("view.zig").View;
const RenderData = struct {
output: *c.wlr_output,
renderer: *c.wlr_renderer,
view: *View,
when: *c.struct_timespec,
};
pub const Output = struct {
const Self = @This();
server: *Server,
wlr_output: *c.wlr_output,
listen_frame: c.wl_listener,
pub fn init(self: *Self, server: *Server, wlr_output: *c.wlr_output) !void {
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
// before we can use the output. The mode is a tuple of (width, height,
// refresh rate), and each monitor supports only a specific set of modes. We
// just pick the monitor's preferred mode, a more sophisticated compositor
// would let the user configure it.
// if not empty
if (c.wl_list_empty(&wlr_output.modes) == 0) {
const mode = c.wlr_output_preferred_mode(wlr_output);
c.wlr_output_set_mode(wlr_output, mode);
c.wlr_output_enable(wlr_output, true);
if (!c.wlr_output_commit(wlr_output)) {
return error.CantCommitWlrOutputMode;
}
}
self.server = server;
self.wlr_output = wlr_output;
// Sets up a listener for the frame notify event.
self.listen_frame.notify = handle_frame;
c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame);
// Add the new output to the layout. The add_auto function arranges outputs
// from left-to-right in the order they appear. A more sophisticated
// compositor would let the user configure the arrangement of outputs in the
// layout.
c.wlr_output_layout_add_auto(server.wlr_output_layout, wlr_output);
// Creating the global adds a wl_output global to the display, which Wayland
// clients can see to find out information about the output (such as
// DPI, scale factor, manufacturer, etc).
c.wlr_output_create_global(wlr_output);
}
fn handle_frame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) 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 output = @fieldParentPtr(Output, "listen_frame", listener.?);
const renderer = output.server.wlr_renderer;
var now: c.struct_timespec = undefined;
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
// wlr_output_attach_render makes the OpenGL context current.
if (!c.wlr_output_attach_render(output.wlr_output, null)) {
return;
}
// The "effective" resolution can change if you rotate your outputs.
var width: c_int = undefined;
var height: c_int = undefined;
c.wlr_output_effective_resolution(output.wlr_output, &width, &height);
// Begin the renderer (calls glViewport and some other GL sanity checks)
c.wlr_renderer_begin(renderer, width, height);
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
c.wlr_renderer_clear(renderer, &color);
// Each subsequent view is rendered on top of the last.
// The first view in the list is "on top" so iterate in reverse.
var it = output.server.views.last;
while (it) |node| : (it = node.prev) {
const view = &node.data;
if (!view.mapped) {
// An unmapped view should not be rendered.
continue;
}
var rdata = RenderData{
.output = output.wlr_output,
.view = view,
.renderer = renderer,
.when = &now,
};
// This calls our render_surface function for each surface among the
// xdg_surface's toplevel and popups.
c.wlr_xdg_surface_for_each_surface(view.wlr_xdg_surface, render_surface, &rdata);
}
// Hardware cursors are rendered by the GPU on a separate plane, and can be
// moved around without re-rendering what's beneath them - which is more
// efficient. However, not all hardware supports hardware cursors. For this
// reason, wlroots provides a software fallback, which we ask it to render
// here. wlr_cursor handles configuring hardware vs software cursors for you,
// and this function is a no-op when hardware cursors are in use.
c.wlr_output_render_software_cursors(output.wlr_output, null);
// Conclude rendering and swap the buffers, showing the final frame
// on-screen.
c.wlr_renderer_end(renderer);
// TODO: handle failure
_ = c.wlr_output_commit(output.wlr_output);
}
fn render_surface(opt_surface: ?*c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void {
// wlroots says this will never be null
const surface = opt_surface.?;
// This function is called for every surface that needs to be rendered.
const rdata = @ptrCast(*RenderData, @alignCast(@alignOf(RenderData), data));
const view = rdata.view;
const output = rdata.output;
// We first obtain a wlr_texture, which is a GPU resource. wlroots
// automatically handles negotiating these with the client. The underlying
// resource could be an opaque handle passed from the client, or the client
// could have sent a pixel buffer which we copied to the GPU, or a few other
// means. You don't have to worry about this, wlroots takes care of it.
const texture = c.wlr_surface_get_texture(surface);
if (texture == null) {
return;
}
// The view has a position in layout coordinates. If you have two displays,
// one next to the other, both 1080p, a view on the rightmost display might
// have layout coordinates of 2000,100. We need to translate that to
// output-local coordinates, or (2000 - 1920).
var ox: f64 = 0.0;
var oy: f64 = 0.0;
c.wlr_output_layout_output_coords(view.server.wlr_output_layout, output, &ox, &oy);
ox += @intToFloat(f64, view.x + sx);
oy += @intToFloat(f64, view.y + sy);
// We also have to apply the scale factor for HiDPI outputs. This is only
// part of the puzzle, TinyWL does not fully support HiDPI.
const box = c.wlr_box{
.x = @floatToInt(c_int, ox * output.scale),
.y = @floatToInt(c_int, oy * output.scale),
.width = @floatToInt(c_int, @intToFloat(f32, surface.current.width) * output.scale),
.height = @floatToInt(c_int, @intToFloat(f32, surface.current.height) * output.scale),
};
// Those familiar with OpenGL are also familiar with the role of matricies
// in graphics programming. We need to prepare a matrix to render the view
// with. 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.
//
// Naturally you can do this any way you like, for example to make a 3D
// compositor.
var matrix: [9]f32 = undefined;
const transform = c.wlr_output_transform_invert(surface.current.transform);
c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &output.transform_matrix);
// This takes our matrix, the texture, and an alpha, and performs the actual
// rendering on the GPU.
_ = c.wlr_render_texture_with_matrix(rdata.renderer, texture, &matrix, 1.0);
// This lets the client know that we've displayed that frame and it can
// prepare another one now if it likes.
c.wlr_surface_send_frame_done(surface, rdata.when);
}
};
|