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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
const std = @import("std");
const c = @import("c.zig");
const LayerSurface = @import("layer_surface.zig").LayerSurface;
const Output = @import("output.zig").Output;
const Server = @import("server.zig").Server;
const View = @import("view.zig").View;
const ViewStack = @import("view_stack.zig").ViewStack;
pub fn renderOutput(output: *Output) void {
const renderer = output.root.server.wlr_renderer;
var now: c.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.0, 0.16862745, 0.21176471, 1.0 };
c.wlr_renderer_clear(renderer, &color);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now);
// The first view in the list is "on top" so iterate in reverse.
var it = ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags);
while (it.next()) |node| {
const view = &node.view;
// 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) {
continue;
}
// Floating views are rendered on top of normal views
if (view.floating) {
continue;
}
renderView(output.*, view, &now);
renderBorders(output.*, view, &now);
}
// Render floating views
it = ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags);
while (it.next()) |node| {
const view = &node.view;
// 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) {
continue;
}
if (!view.floating) {
continue;
}
renderView(output.*, view, &now);
renderBorders(output.*, view, &now);
}
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now);
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now);
// 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);
}
const LayerSurfaceRenderData = struct {
output: *c.wlr_output,
renderer: *c.wlr_renderer,
layer_surface: *LayerSurface,
when: *c.timespec,
};
/// Render all surfaces on the passed layer
fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void {
var it = layer.first;
while (it) |node| : (it = node.next) {
const layer_surface = &node.data;
if (!layer_surface.mapped) {
continue;
}
var rdata = LayerSurfaceRenderData{
.output = output.wlr_output,
.renderer = output.root.server.wlr_renderer,
.layer_surface = layer_surface,
.when = now,
};
c.wlr_layer_surface_v1_for_each_surface(
layer_surface.wlr_layer_surface,
renderLayerSurface,
&rdata,
);
}
}
/// This function is called for every layer surface and popup that needs to be rendered.
/// TODO: refactor this to reduce code duplication
fn renderLayerSurface(_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 = _surface.?;
// This function is called for every surface that needs to be rendered.
const rdata = @ptrCast(*LayerSurfaceRenderData, @alignCast(@alignOf(LayerSurfaceRenderData), data));
const layer_surface = rdata.layer_surface;
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;
}
var box = c.wlr_box{
.x = layer_surface.box.x + sx,
.y = layer_surface.box.y + sy,
.width = surface.current.width,
.height = surface.current.height,
};
// Scale the box to the output's current scaling factor
scaleBox(&box, 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 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);
}
const ViewRenderData = struct {
output: *c.wlr_output,
renderer: *c.wlr_renderer,
view: *View,
when: *c.timespec,
};
fn renderView(output: Output, view: *View, now: *c.timespec) void {
// If we have a stashed buffer, we are in the middle of a transaction
// and need to render that buffer until the transaction is complete.
if (view.stashed_buffer) |buffer| {
const border_width = view.output.root.server.config.border_width;
const view_padding = view.output.root.server.config.view_padding;
var box = c.wlr_box{
.x = view.current_box.x + @intCast(i32, border_width + view_padding),
.y = view.current_box.y + @intCast(i32, border_width + view_padding),
.width = @intCast(c_int, view.current_box.width - border_width * 2 - view_padding * 2),
.height = @intCast(c_int, view.current_box.height - border_width * 2 - view_padding * 2),
};
// Scale the box to the output's current scaling factor
scaleBox(&box, output.wlr_output.scale);
var matrix: [9]f32 = undefined;
c.wlr_matrix_project_box(
&matrix,
&box,
c.enum_wl_output_transform.WL_OUTPUT_TRANSFORM_NORMAL,
0.0,
&output.wlr_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(
output.root.server.wlr_renderer,
buffer.texture,
&matrix,
1.0,
);
} else {
// Since there is no stashed buffer, we are not in the middle of
// a transaction and may simply render each toplevel surface.
var rdata = ViewRenderData{
.output = output.wlr_output,
.view = view,
.renderer = output.root.server.wlr_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, renderSurface, &rdata);
}
}
/// This function is called for every toplevel and popup surface that needs to be rendered.
fn renderSurface(_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 = _surface.?;
const rdata = @ptrCast(*ViewRenderData, @alignCast(@alignOf(ViewRenderData), 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;
}
const border_width = view.output.root.server.config.border_width;
const view_padding = view.output.root.server.config.view_padding;
var box = c.wlr_box{
.x = view.current_box.x + sx + @intCast(c_int, border_width + view_padding),
.y = view.current_box.y + sy + @intCast(c_int, border_width + view_padding),
.width = surface.current.width,
.height = surface.current.height,
};
// Scale the box to the output's current scaling factor
scaleBox(&box, 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 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);
}
fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
var border: c.wlr_box = undefined;
const color = if (view.focused)
[_]f32{ 0.57647059, 0.63137255, 0.63137255, 1.0 } // Solarized base1
else
[_]f32{ 0.34509804, 0.43137255, 0.45882353, 1.0 }; // Solarized base01
const border_width = output.root.server.config.border_width;
const view_padding = output.root.server.config.view_padding;
// left border
border.x = view.current_box.x + @intCast(c_int, view_padding);
border.y = view.current_box.y + @intCast(c_int, view_padding);
border.width = @intCast(c_int, border_width);
border.height = @intCast(c_int, view.current_box.height - view_padding * 2);
scaleBox(&border, output.wlr_output.scale);
c.wlr_render_rect(
output.root.server.wlr_renderer,
&border,
&color,
&output.wlr_output.transform_matrix,
);
// right border
border.x = view.current_box.x +
@intCast(c_int, view.current_box.width - border_width - view_padding);
border.y = view.current_box.y + @intCast(c_int, view_padding);
border.width = @intCast(c_int, border_width);
border.height = @intCast(c_int, view.current_box.height - view_padding * 2);
scaleBox(&border, output.wlr_output.scale);
c.wlr_render_rect(
output.root.server.wlr_renderer,
&border,
&color,
&output.wlr_output.transform_matrix,
);
// top border
border.x = view.current_box.x + @intCast(c_int, border_width + view_padding);
border.y = view.current_box.y + @intCast(c_int, view_padding);
border.width = @intCast(c_int, view.current_box.width -
border_width * 2 - view_padding * 2);
border.height = @intCast(c_int, border_width);
scaleBox(&border, output.wlr_output.scale);
c.wlr_render_rect(
output.root.server.wlr_renderer,
&border,
&color,
&output.wlr_output.transform_matrix,
);
// bottom border
border.x = view.current_box.x + @intCast(c_int, border_width + view_padding);
border.y = view.current_box.y +
@intCast(c_int, view.current_box.height - border_width - view_padding);
border.width = @intCast(c_int, view.current_box.width -
border_width * 2 - view_padding * 2);
border.height = @intCast(c_int, border_width);
scaleBox(&border, output.wlr_output.scale);
c.wlr_render_rect(
output.root.server.wlr_renderer,
&border,
&color,
&output.wlr_output.transform_matrix,
);
}
/// Scale a wlr_box, taking the possibility of fractional scaling into account.
fn scaleBox(box: *c.wlr_box, scale: f64) void {
box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale));
box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale));
box.width = scaleLength(box.width, box.x, scale);
box.height = scaleLength(box.height, box.x, 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));
}
|