diff options
Diffstat (limited to 'sway/desktop/transaction.c')
-rw-r--r-- | sway/desktop/transaction.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c new file mode 100644 index 00000000..d2932c87 --- /dev/null +++ b/sway/desktop/transaction.c | |||
@@ -0,0 +1,415 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <stdbool.h> | ||
3 | #include <stdlib.h> | ||
4 | #include <string.h> | ||
5 | #include <time.h> | ||
6 | #include <wlr/types/wlr_buffer.h> | ||
7 | #include <wlr/types/wlr_linux_dmabuf.h> | ||
8 | #include "sway/debug.h" | ||
9 | #include "sway/desktop/transaction.h" | ||
10 | #include "sway/output.h" | ||
11 | #include "sway/tree/container.h" | ||
12 | #include "sway/tree/view.h" | ||
13 | #include "sway/tree/workspace.h" | ||
14 | #include "list.h" | ||
15 | #include "log.h" | ||
16 | |||
17 | /** | ||
18 | * How long we should wait for views to respond to the configure before giving | ||
19 | * up and applying the transaction anyway. | ||
20 | */ | ||
21 | #define TIMEOUT_MS 200 | ||
22 | |||
23 | /** | ||
24 | * If enabled, sway will always wait for the transaction timeout before | ||
25 | * applying it, rather than applying it when the views are ready. This allows us | ||
26 | * to observe the rendered state while a transaction is in progress. | ||
27 | */ | ||
28 | #define TRANSACTION_DEBUG false | ||
29 | |||
30 | struct sway_transaction { | ||
31 | struct wl_event_source *timer; | ||
32 | list_t *instructions; // struct sway_transaction_instruction * | ||
33 | size_t num_waiting; | ||
34 | size_t num_configures; | ||
35 | struct timespec create_time; | ||
36 | struct timespec commit_time; | ||
37 | }; | ||
38 | |||
39 | struct sway_transaction_instruction { | ||
40 | struct sway_transaction *transaction; | ||
41 | struct sway_container *container; | ||
42 | struct sway_container_state state; | ||
43 | struct wlr_buffer *saved_buffer; | ||
44 | int saved_buffer_width, saved_buffer_height; | ||
45 | uint32_t serial; | ||
46 | bool ready; | ||
47 | }; | ||
48 | |||
49 | struct sway_transaction *transaction_create() { | ||
50 | struct sway_transaction *transaction = | ||
51 | calloc(1, sizeof(struct sway_transaction)); | ||
52 | transaction->instructions = create_list(); | ||
53 | if (server.debug_txn_timings) { | ||
54 | clock_gettime(CLOCK_MONOTONIC, &transaction->create_time); | ||
55 | } | ||
56 | return transaction; | ||
57 | } | ||
58 | |||
59 | static void remove_saved_view_buffer( | ||
60 | struct sway_transaction_instruction *instruction) { | ||
61 | if (instruction->saved_buffer) { | ||
62 | wlr_buffer_unref(instruction->saved_buffer); | ||
63 | instruction->saved_buffer = NULL; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | static void save_view_buffer(struct sway_view *view, | ||
68 | struct sway_transaction_instruction *instruction) { | ||
69 | if (!sway_assert(instruction->saved_buffer == NULL, | ||
70 | "Didn't expect instruction to have a saved buffer already")) { | ||
71 | remove_saved_view_buffer(instruction); | ||
72 | } | ||
73 | if (view->surface && wlr_surface_has_buffer(view->surface)) { | ||
74 | instruction->saved_buffer = wlr_buffer_ref(view->surface->buffer); | ||
75 | instruction->saved_buffer_width = view->surface->current->width; | ||
76 | instruction->saved_buffer_height = view->surface->current->height; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | static void transaction_destroy(struct sway_transaction *transaction) { | ||
81 | // Free instructions | ||
82 | for (int i = 0; i < transaction->instructions->length; ++i) { | ||
83 | struct sway_transaction_instruction *instruction = | ||
84 | transaction->instructions->items[i]; | ||
85 | struct sway_container *con = instruction->container; | ||
86 | for (int j = 0; j < con->instructions->length; ++j) { | ||
87 | if (con->instructions->items[j] == instruction) { | ||
88 | list_del(con->instructions, j); | ||
89 | break; | ||
90 | } | ||
91 | } | ||
92 | if (con->destroying && !con->instructions->length) { | ||
93 | container_free(con); | ||
94 | } | ||
95 | remove_saved_view_buffer(instruction); | ||
96 | free(instruction); | ||
97 | } | ||
98 | list_free(transaction->instructions); | ||
99 | |||
100 | if (transaction->timer) { | ||
101 | wl_event_source_remove(transaction->timer); | ||
102 | } | ||
103 | free(transaction); | ||
104 | } | ||
105 | |||
106 | static void copy_pending_state(struct sway_container *container, | ||
107 | struct sway_container_state *state) { | ||
108 | state->layout = container->layout; | ||
109 | state->swayc_x = container->x; | ||
110 | state->swayc_y = container->y; | ||
111 | state->swayc_width = container->width; | ||
112 | state->swayc_height = container->height; | ||
113 | state->has_gaps = container->has_gaps; | ||
114 | state->current_gaps = container->current_gaps; | ||
115 | state->gaps_inner = container->gaps_inner; | ||
116 | state->gaps_outer = container->gaps_outer; | ||
117 | state->parent = container->parent; | ||
118 | |||
119 | if (container->type == C_VIEW) { | ||
120 | struct sway_view *view = container->sway_view; | ||
121 | state->view_x = view->x; | ||
122 | state->view_y = view->y; | ||
123 | state->view_width = view->width; | ||
124 | state->view_height = view->height; | ||
125 | state->is_fullscreen = view->is_fullscreen; | ||
126 | state->border = view->border; | ||
127 | state->border_thickness = view->border_thickness; | ||
128 | state->border_top = view->border_top; | ||
129 | state->border_left = view->border_left; | ||
130 | state->border_right = view->border_right; | ||
131 | state->border_bottom = view->border_bottom; | ||
132 | } else if (container->type == C_WORKSPACE) { | ||
133 | state->ws_fullscreen = container->sway_workspace->fullscreen; | ||
134 | state->ws_floating = container->sway_workspace->floating; | ||
135 | state->children = create_list(); | ||
136 | list_cat(state->children, container->children); | ||
137 | } else { | ||
138 | state->children = create_list(); | ||
139 | list_cat(state->children, container->children); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | static bool transaction_has_container(struct sway_transaction *transaction, | ||
144 | struct sway_container *container) { | ||
145 | for (int i = 0; i < transaction->instructions->length; ++i) { | ||
146 | struct sway_transaction_instruction *instruction = | ||
147 | transaction->instructions->items[i]; | ||
148 | if (instruction->container == container) { | ||
149 | return true; | ||
150 | } | ||
151 | } | ||
152 | return false; | ||
153 | } | ||
154 | |||
155 | void transaction_add_container(struct sway_transaction *transaction, | ||
156 | struct sway_container *container) { | ||
157 | if (transaction_has_container(transaction, container)) { | ||
158 | return; | ||
159 | } | ||
160 | struct sway_transaction_instruction *instruction = | ||
161 | calloc(1, sizeof(struct sway_transaction_instruction)); | ||
162 | instruction->transaction = transaction; | ||
163 | instruction->container = container; | ||
164 | |||
165 | copy_pending_state(container, &instruction->state); | ||
166 | |||
167 | if (container->type == C_VIEW) { | ||
168 | save_view_buffer(container->sway_view, instruction); | ||
169 | } | ||
170 | list_add(transaction->instructions, instruction); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Apply a transaction to the "current" state of the tree. | ||
175 | */ | ||
176 | static void transaction_apply(struct sway_transaction *transaction) { | ||
177 | wlr_log(L_DEBUG, "Applying transaction %p", transaction); | ||
178 | if (server.debug_txn_timings) { | ||
179 | struct timespec now; | ||
180 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
181 | struct timespec *create = &transaction->create_time; | ||
182 | struct timespec *commit = &transaction->commit_time; | ||
183 | float ms_arranging = (commit->tv_sec - create->tv_sec) * 1000 + | ||
184 | (commit->tv_nsec - create->tv_nsec) / 1000000.0; | ||
185 | float ms_waiting = (now.tv_sec - commit->tv_sec) * 1000 + | ||
186 | (now.tv_nsec - commit->tv_nsec) / 1000000.0; | ||
187 | float ms_total = ms_arranging + ms_waiting; | ||
188 | wlr_log(L_DEBUG, "Transaction %p: %.1fms arranging, %.1fms waiting, " | ||
189 | "%.1fms total (%.1f frames if 60Hz)", transaction, | ||
190 | ms_arranging, ms_waiting, ms_total, ms_total / (1000 / 60)); | ||
191 | } | ||
192 | |||
193 | // Apply the instruction state to the container's current state | ||
194 | for (int i = 0; i < transaction->instructions->length; ++i) { | ||
195 | struct sway_transaction_instruction *instruction = | ||
196 | transaction->instructions->items[i]; | ||
197 | struct sway_container *container = instruction->container; | ||
198 | |||
199 | // Damage the old and new locations | ||
200 | struct wlr_box old_box = { | ||
201 | .x = container->current.swayc_x, | ||
202 | .y = container->current.swayc_y, | ||
203 | .width = container->current.swayc_width, | ||
204 | .height = container->current.swayc_height, | ||
205 | }; | ||
206 | struct wlr_box new_box = { | ||
207 | .x = instruction->state.swayc_x, | ||
208 | .y = instruction->state.swayc_y, | ||
209 | .width = instruction->state.swayc_width, | ||
210 | .height = instruction->state.swayc_height, | ||
211 | }; | ||
212 | for (int j = 0; j < root_container.children->length; ++j) { | ||
213 | struct sway_container *output = root_container.children->items[j]; | ||
214 | output_damage_box(output->sway_output, &old_box); | ||
215 | output_damage_box(output->sway_output, &new_box); | ||
216 | } | ||
217 | |||
218 | // There are separate children lists for each instruction state, the | ||
219 | // container's current state and the container's pending state | ||
220 | // (ie. con->children). The list itself needs to be freed here. | ||
221 | // Any child containers which are being deleted will be cleaned up in | ||
222 | // transaction_destroy(). | ||
223 | list_free(container->current.children); | ||
224 | |||
225 | memcpy(&container->current, &instruction->state, | ||
226 | sizeof(struct sway_container_state)); | ||
227 | } | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * For simplicity, we only progress the queue if it can be completely flushed. | ||
232 | */ | ||
233 | static void transaction_progress_queue() { | ||
234 | // We iterate this list in reverse because we're more likely to find a | ||
235 | // waiting transactions at the end of the list. | ||
236 | for (int i = server.transactions->length - 1; i >= 0; --i) { | ||
237 | struct sway_transaction *transaction = server.transactions->items[i]; | ||
238 | if (transaction->num_waiting) { | ||
239 | return; | ||
240 | } | ||
241 | } | ||
242 | for (int i = 0; i < server.transactions->length; ++i) { | ||
243 | struct sway_transaction *transaction = server.transactions->items[i]; | ||
244 | transaction_apply(transaction); | ||
245 | transaction_destroy(transaction); | ||
246 | } | ||
247 | server.transactions->length = 0; | ||
248 | } | ||
249 | |||
250 | static int handle_timeout(void *data) { | ||
251 | struct sway_transaction *transaction = data; | ||
252 | wlr_log(L_DEBUG, "Transaction %p timed out (%li waiting)", | ||
253 | transaction, transaction->num_waiting); | ||
254 | transaction->num_waiting = 0; | ||
255 | transaction_progress_queue(); | ||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | static bool should_configure(struct sway_container *con, | ||
260 | struct sway_transaction_instruction *instruction) { | ||
261 | if (con->type != C_VIEW) { | ||
262 | return false; | ||
263 | } | ||
264 | if (con->destroying) { | ||
265 | return false; | ||
266 | } | ||
267 | // The settled dimensions are what size the view will be once any pending | ||
268 | // configures have applied (excluding the one we might be configuring now). | ||
269 | // If these match the dimensions that this transaction wants then we don't | ||
270 | // need to configure it. | ||
271 | int settled_width = con->current.view_width; | ||
272 | int settled_height = con->current.view_height; | ||
273 | if (con->instructions->length) { | ||
274 | struct sway_transaction_instruction *last_instruction = | ||
275 | con->instructions->items[con->instructions->length - 1]; | ||
276 | settled_width = last_instruction->state.view_width; | ||
277 | settled_height = last_instruction->state.view_height; | ||
278 | } | ||
279 | if (settled_width == instruction->state.view_width && | ||
280 | settled_height == instruction->state.view_height) { | ||
281 | return false; | ||
282 | } | ||
283 | return true; | ||
284 | } | ||
285 | |||
286 | void transaction_commit(struct sway_transaction *transaction) { | ||
287 | wlr_log(L_DEBUG, "Transaction %p committing with %i instructions", | ||
288 | transaction, transaction->instructions->length); | ||
289 | transaction->num_waiting = 0; | ||
290 | for (int i = 0; i < transaction->instructions->length; ++i) { | ||
291 | struct sway_transaction_instruction *instruction = | ||
292 | transaction->instructions->items[i]; | ||
293 | struct sway_container *con = instruction->container; | ||
294 | if (should_configure(con, instruction)) { | ||
295 | instruction->serial = view_configure(con->sway_view, | ||
296 | instruction->state.view_x, | ||
297 | instruction->state.view_y, | ||
298 | instruction->state.view_width, | ||
299 | instruction->state.view_height); | ||
300 | ++transaction->num_waiting; | ||
301 | |||
302 | // From here on we are rendering a saved buffer of the view, which | ||
303 | // means we can send a frame done event to make the client redraw it | ||
304 | // as soon as possible. Additionally, this is required if a view is | ||
305 | // mapping and its default geometry doesn't intersect an output. | ||
306 | struct timespec when; | ||
307 | wlr_surface_send_frame_done(con->sway_view->surface, &when); | ||
308 | } | ||
309 | list_add(con->instructions, instruction); | ||
310 | } | ||
311 | transaction->num_configures = transaction->num_waiting; | ||
312 | if (server.debug_txn_timings) { | ||
313 | clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time); | ||
314 | } | ||
315 | if (server.transactions->length || transaction->num_waiting) { | ||
316 | list_add(server.transactions, transaction); | ||
317 | } else { | ||
318 | // There are no other transactions in progress, and this one has nothing | ||
319 | // to wait for, so we can skip the queue. | ||
320 | wlr_log(L_DEBUG, "Transaction %p has nothing to wait for", transaction); | ||
321 | transaction_apply(transaction); | ||
322 | transaction_destroy(transaction); | ||
323 | return; | ||
324 | } | ||
325 | |||
326 | if (transaction->num_waiting) { | ||
327 | // Set up a timer which the views must respond within | ||
328 | transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, | ||
329 | handle_timeout, transaction); | ||
330 | wl_event_source_timer_update(transaction->timer, TIMEOUT_MS); | ||
331 | } | ||
332 | |||
333 | // The debug tree shows the pending/live tree. Here is a good place to | ||
334 | // update it, because we make a transaction every time we change the pending | ||
335 | // tree. | ||
336 | update_debug_tree(); | ||
337 | } | ||
338 | |||
339 | static void set_instruction_ready( | ||
340 | struct sway_transaction_instruction *instruction) { | ||
341 | instruction->ready = true; | ||
342 | struct sway_transaction *transaction = instruction->transaction; | ||
343 | |||
344 | if (server.debug_txn_timings) { | ||
345 | struct timespec now; | ||
346 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
347 | struct timespec *start = &transaction->commit_time; | ||
348 | float ms = (now.tv_sec - start->tv_sec) * 1000 + | ||
349 | (now.tv_nsec - start->tv_nsec) / 1000000.0; | ||
350 | wlr_log(L_DEBUG, "Transaction %p: %li/%li ready in %.1fms (%s)", | ||
351 | transaction, | ||
352 | transaction->num_configures - transaction->num_waiting + 1, | ||
353 | transaction->num_configures, ms, | ||
354 | instruction->container->name); | ||
355 | |||
356 | } | ||
357 | |||
358 | // If all views are ready, apply the transaction. | ||
359 | // If the transaction has timed out then its num_waiting will be 0 already. | ||
360 | if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { | ||
361 | #if !TRANSACTION_DEBUG | ||
362 | wlr_log(L_DEBUG, "Transaction %p is ready", transaction); | ||
363 | wl_event_source_timer_update(transaction->timer, 0); | ||
364 | transaction_progress_queue(); | ||
365 | #endif | ||
366 | } | ||
367 | } | ||
368 | |||
369 | /** | ||
370 | * Mark all of the view's instructions as ready up to and including the | ||
371 | * instruction at the given index. This allows the view to skip a configure. | ||
372 | */ | ||
373 | static void set_instructions_ready(struct sway_view *view, int index) { | ||
374 | for (int i = 0; i <= index; ++i) { | ||
375 | struct sway_transaction_instruction *instruction = | ||
376 | view->swayc->instructions->items[i]; | ||
377 | set_instruction_ready(instruction); | ||
378 | } | ||
379 | } | ||
380 | |||
381 | void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) { | ||
382 | for (int i = 0; i < view->swayc->instructions->length; ++i) { | ||
383 | struct sway_transaction_instruction *instruction = | ||
384 | view->swayc->instructions->items[i]; | ||
385 | if (instruction->serial == serial && !instruction->ready) { | ||
386 | set_instructions_ready(view, i); | ||
387 | return; | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | void transaction_notify_view_ready_by_size(struct sway_view *view, | ||
393 | int width, int height) { | ||
394 | for (int i = 0; i < view->swayc->instructions->length; ++i) { | ||
395 | struct sway_transaction_instruction *instruction = | ||
396 | view->swayc->instructions->items[i]; | ||
397 | if (!instruction->ready && instruction->state.view_width == width && | ||
398 | instruction->state.view_height == height) { | ||
399 | set_instructions_ready(view, i); | ||
400 | return; | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | |||
405 | struct wlr_texture *transaction_get_saved_texture(struct sway_view *view, | ||
406 | int *width, int *height) { | ||
407 | struct sway_transaction_instruction *instruction = | ||
408 | view->swayc->instructions->items[0]; | ||
409 | if (!instruction->saved_buffer) { | ||
410 | return NULL; | ||
411 | } | ||
412 | *width = instruction->saved_buffer_width; | ||
413 | *height = instruction->saved_buffer_height; | ||
414 | return instruction->saved_buffer->texture; | ||
415 | } | ||