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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
|
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <[email protected]>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include "backend/backend.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "log.h"
#include "region.h"
#include "types.h"
#include "win.h"
#include "x.h"
extern struct backend_operations xrender_ops, dummy_ops;
#ifdef CONFIG_OPENGL
extern struct backend_operations glx_ops;
extern struct backend_operations egl_ops;
#endif
struct backend_operations *backend_list[NUM_BKEND] = {
[BKEND_XRENDER] = &xrender_ops,
[BKEND_DUMMY] = &dummy_ops,
#ifdef CONFIG_OPENGL
[BKEND_GLX] = &glx_ops,
[BKEND_EGL] = &egl_ops,
#endif
};
/**
* @param all_damage if true ignore damage and repaint the whole screen
*/
region_t get_damage(session_t *ps, bool all_damage) {
region_t region;
auto buffer_age_fn = ps->backend_data->ops->buffer_age;
int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1;
if (all_damage) {
buffer_age = -1;
}
pixman_region32_init(®ion);
if (buffer_age == -1 || buffer_age > ps->ndamage) {
pixman_region32_copy(®ion, &ps->screen_reg);
} else {
for (int i = 0; i < buffer_age; i++) {
auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
log_trace("damage index: %d, damage ring offset: %td", i, curr);
dump_region(&ps->damage_ring[curr]);
pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]);
}
pixman_region32_intersect(®ion, ®ion, &ps->screen_reg);
}
return region;
}
void handle_device_reset(session_t *ps) {
log_error("Device reset detected");
// Wait for reset to complete
// Although ideally the backend should return DEVICE_STATUS_NORMAL after a reset
// is completed, it's not always possible.
//
// According to ARB_robustness (emphasis mine):
//
// "If a reset status other than NO_ERROR is returned and subsequent
// calls return NO_ERROR, the context reset was encountered and
// completed. If a reset status is repeatedly returned, the context **may**
// be in the process of resetting."
//
// Which means it may also not be in the process of resetting. For example on
// AMDGPU devices, Mesa OpenGL always return CONTEXT_RESET after a reset has
// started, completed or not.
//
// So here we blindly wait 5 seconds and hope ourselves best of the luck.
sleep(5);
// Reset compfy
log_info("Resetting compfy after device reset");
ev_break(ps->loop, EVBREAK_ALL);
}
static void process_window_for_painting(session_t *ps, struct managed_win* w, void* win_image,
double additional_alpha,
region_t* reg_bound, region_t* reg_visible,
region_t* reg_paint, region_t* reg_paint_in_bound) {
// For window image processing, we don't have to limit the process
// region to damage for correctness. (see <damager-note> for
// details)
// The visible region, in window local coordinates Although we
// don't limit process region to damage, we provide that info in
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
{
// The bounding shape, in window local coordinates
region_t reg_bound_local;
pixman_region32_init(®_bound_local);
pixman_region32_copy(®_bound_local, reg_bound);
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(®_visible_local);
pixman_region32_intersect(®_visible_local,
reg_visible, reg_paint);
pixman_region32_translate(®_visible_local, -w->g.x,
-w->g.y);
// Data outside of the bounding shape won't be visible,
// but it is not necessary to limit the image operations
// to the bounding shape yet. So pass that as the visible
// region, not the clip region.
pixman_region32_intersect(
®_visible_local, ®_visible_local, ®_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, win_image, ®_visible_local);
auto reg_frame = win_get_region_frame_local_by_val(w);
double alpha = additional_alpha*w->opacity;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame,
®_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(®_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, window_coord,
reg_paint_in_bound, reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(®_visible_local);
}
/// paint all windows
void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
if (ps->backend_data->ops->device_status &&
ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
return handle_device_reset(ps);
}
if (ps->o.xrender_sync_fence) {
if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
log_error("x_fence_sync failed, xrender-sync-fence will be "
"disabled from now on.");
xcb_sync_destroy_fence(ps->c, ps->sync_fence);
ps->sync_fence = XCB_NONE;
ps->o.xrender_sync_fence = false;
ps->xsync_exists = false;
}
}
// All painting will be limited to the damage, if _some_ of
// the paints bleed out of the damage region, it will destroy
// part of the image we want to reuse
region_t reg_damage;
if (!ignore_damage) {
reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
} else {
pixman_region32_init(®_damage);
pixman_region32_copy(®_damage, &ps->screen_reg);
}
if (!pixman_region32_not_empty(®_damage)) {
pixman_region32_fini(®_damage);
return;
}
#ifdef DEBUG_REPAINT
static struct timespec last_paint = {0};
#endif
// <damage-note>
// If use_damage is enabled, we MUST make sure only the damaged regions of the
// screen are ever touched by the compositor. The reason is that at the beginning
// of each render, we clear the damaged regions with the wallpaper, and nothing
// else. If later during the render we changed anything outside the damaged
// region, that won't be cleared by the next render, and will thus accumulate.
// (e.g. if shadow is drawn outside the damaged region, it will become thicker and
// thicker over time.)
/// The adjusted damaged regions
region_t reg_paint;
assert(ps->o.blur_method != BLUR_METHOD_INVALID);
if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) {
int blur_width, blur_height;
ps->backend_data->ops->get_blur_size(ps->backend_blur_context,
&blur_width, &blur_height);
// The region of screen a given window influences will be smeared
// out by blur. With more windows on top of the given window, the
// influences region will be smeared out more.
//
// Also, blurring requires data slightly outside the area that needs
// to be blurred. The more semi-transparent windows are stacked on top
// of each other, the larger the area will be.
//
// Instead of accurately calculate how much bigger the damage
// region will be because of blur, we assume the worst case here.
// That is, the damaged window is at the bottom of the stack, and
// all other windows have semi-transparent background
int resize_factor = 1;
if (t) {
resize_factor = t->stacking_rank;
}
resize_region_in_place(®_damage, blur_width * resize_factor,
blur_height * resize_factor);
reg_paint = resize_region(®_damage, blur_width * resize_factor,
blur_height * resize_factor);
pixman_region32_intersect(®_paint, ®_paint, &ps->screen_reg);
pixman_region32_intersect(®_damage, ®_damage, &ps->screen_reg);
} else {
pixman_region32_init(®_paint);
pixman_region32_copy(®_paint, ®_damage);
}
// A hint to backend, the region that will be visible on screen
// backend can optimize based on this info
region_t reg_visible;
pixman_region32_init(®_visible);
pixman_region32_copy(®_visible, &ps->screen_reg);
if (t && !ps->o.transparent_clipping) {
// Calculate the region upon which the root window (wallpaper) is to be
// painted based on the ignore region of the lowest window, if available
//
// NOTE If transparent_clipping is enabled, transparent windows are
// included in the reg_ignore, but we still want to have the wallpaper
// beneath them, so we don't use reg_ignore for wallpaper in that case.
pixman_region32_subtract(®_visible, ®_visible, t->reg_ignore);
}
// Region on screen we don't want any shadows on
region_t reg_shadow_clip;
pixman_region32_init(®_shadow_clip);
if (ps->backend_data->ops->prepare) {
ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
}
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
(coord_t){0}, NULL, (coord_t){0},
®_paint, ®_visible);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
®_paint);
}
// Windows are sorted from bottom to top
// Each window has a reg_ignore, which is the region obscured by all the windows
// on top of that window. This is used to reduce the number of pixels painted.
//
// Whether this is beneficial is to be determined XXX
for (auto w = t; w; w = w->prev_trans) {
pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore);
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE));
// The bounding shape of the window, in global/target coordinates
// reminder: bounding shape contains the WM frame
auto reg_bound = win_get_bounding_shape_global_by_val(w);
auto reg_bound_no_corner =
win_get_bounding_shape_global_without_corners_by_val(w);
if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) {
win_bind_mask(ps->backend_data, w);
}
// The clip region for the current window, in global/target coordinates
// reg_paint_in_bound \in reg_paint
region_t reg_paint_in_bound;
pixman_region32_init(®_paint_in_bound);
pixman_region32_intersect(®_paint_in_bound, ®_bound, ®_paint);
if (ps->o.transparent_clipping) {
// <transparent-clipping-note>
// If transparent_clipping is enabled, we need to be SURE that
// things are not drawn inside reg_ignore, because otherwise they
// will appear underneath transparent windows.
// So here we have make sure reg_paint_in_bound \in reg_visible
// There are a few other places below where this is needed as
// well.
pixman_region32_intersect(®_paint_in_bound,
®_paint_in_bound, ®_visible);
}
// Blur window background
/* TODO(yshui) since the backend might change the content of the window
* (e.g. with shaders), we should consult the backend whether the window
* is transparent or not. for now we will just rely on the force_win_blend
* option */
auto real_win_mode = w->mode;
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
(ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
// Minimize the region we try to blur, if the window
// itself is not opaque, only the frame is.
double blur_opacity = 1;
if (w->opacity < (1.0 / MAX_ALPHA)) {
// Hide blur for fully transparent windows.
blur_opacity = 0;
} else if (w->state == WSTATE_MAPPING) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->state == WSTATE_UNMAPPING ||
w->state == WSTATE_DESTROYING) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
} else if (w->state == WSTATE_FADING) {
if (w->opacity < w->opacity_target &&
w->opacity_target_old < (1.0 / MAX_ALPHA)) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->opacity > w->opacity_target &&
w->opacity_target < (1.0 / MAX_ALPHA)) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
}
}
assert(blur_opacity >= 0 && blur_opacity <= 1);
if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
// We need to blur the bounding shape of the window
// (reg_paint_in_bound = reg_bound \cap reg_paint)
ps->backend_data->ops->blur(
ps->backend_data, blur_opacity,
ps->backend_blur_context, w->mask_image, window_coord,
®_paint_in_bound, ®_visible);
} else {
// Window itself is solid, we only need to blur the frame
// region
// Readability assertions
assert(ps->o.blur_background_frame);
assert(real_win_mode == WMODE_FRAME_TRANS);
auto reg_blur = win_get_region_frame_local_by_val(w);
pixman_region32_translate(®_blur, w->g.x, w->g.y);
// make sure reg_blur \in reg_paint
pixman_region32_intersect(®_blur, ®_blur, ®_paint);
if (ps->o.transparent_clipping) {
// ref: <transparent-clipping-note>
pixman_region32_intersect(®_blur, ®_blur,
®_visible);
}
ps->backend_data->ops->blur(
ps->backend_data, blur_opacity, ps->backend_blur_context,
w->mask_image, window_coord, ®_blur, ®_visible);
pixman_region32_fini(®_blur);
}
}
// Draw shadow on target
if (w->shadow) {
assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
// Clip region for the shadow
// reg_shadow \in reg_paint
auto reg_shadow = win_extents_by_val(w);
pixman_region32_intersect(®_shadow, ®_shadow, ®_paint);
// Mask out the region we don't want shadow on
if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
pixman_region32_subtract(®_shadow, ®_shadow,
&ps->shadow_exclude_reg);
}
if (pixman_region32_not_empty(®_shadow_clip)) {
pixman_region32_subtract(®_shadow, ®_shadow,
®_shadow_clip);
}
if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
w->xinerama_scr < ps->xinerama_nscrs) {
// There can be a window where number of screens is
// updated, but the screen number attached to the windows
// have not.
//
// Window screen number will be updated eventually, so
// here we just check to make sure we don't access out of
// bounds.
pixman_region32_intersect(
®_shadow, ®_shadow,
&ps->xinerama_scr_regs[w->xinerama_scr]);
}
if (ps->o.transparent_clipping) {
// ref: <transparent-clipping-note>
pixman_region32_intersect(®_shadow, ®_shadow,
®_visible);
}
assert(w->shadow_image);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image,
&w->opacity);
coord_t shadow_coord = {.x = w->g.x + w->shadow_dx,
.y = w->g.y + w->shadow_dy};
auto inverted_mask = NULL;
if (!ps->o.wintype_option[w->window_type].full_shadow) {
pixman_region32_subtract(®_shadow, ®_shadow,
®_bound_no_corner);
if (w->mask_image) {
inverted_mask = w->mask_image;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
inverted_mask, (bool[]){true});
}
}
ps->backend_data->ops->compose(
ps->backend_data, w->shadow_image, shadow_coord,
inverted_mask, window_coord, ®_shadow, ®_visible);
if (inverted_mask) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
inverted_mask, (bool[]){false});
}
pixman_region32_fini(®_shadow);
}
// Update image properties
{
double dim_opacity = 0.0;
if (w->dim) {
dim_opacity = ps->o.inactive_dim;
if (!ps->o.inactive_dim_fixed) {
dim_opacity *= w->opacity;
}
}
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image,
&ps->o.max_brightness);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED, w->win_image,
&w->invert_color);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image,
&dim_opacity);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image,
(double[]){w->corner_radius});
if (w->corner_radius) {
int border_width = w->g.border_width;
if (border_width == 0) {
// Some WM has borders implemented as WM frames
border_width = min3(w->frame_extents.left,
w->frame_extents.right,
w->frame_extents.bottom);
}
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->win_image, &border_width);
}
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image,
w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
}
if (w->opacity * MAX_ALPHA < 1) {
// We don't need to paint the window body itself if it's
// completely transparent.
goto skip;
}
if (w->clip_shadow_above) {
// Add window bounds to shadow-clip region
pixman_region32_union(®_shadow_clip, ®_shadow_clip, ®_bound);
} else {
// Remove overlapping window bounds from shadow-clip region
pixman_region32_subtract(®_shadow_clip, ®_shadow_clip, ®_bound);
}
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
// Draw window on target
if (w->frame_opacity == 1 && !is_animating) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
window_coord, NULL, window_coord,
®_paint_in_bound, ®_visible);
} else {
if (is_animating && w->old_win_image) {
assert(w->old_win_image);
bool resizing =
w->g.width != w->pending_g.width ||
w->g.height != w->pending_g.height;
// Only animate opacity here if we are resizing
// a transparent window
process_window_for_painting(ps, w, w->win_image,
1,
®_bound, ®_visible,
®_paint, ®_paint_in_bound);
// Only do this if size changes as otherwise moving
// transparent windows will flicker and if you just
// move so slightly they will keep flickering
if (resizing) {
process_window_for_painting(ps, w, w->old_win_image,
1.0 - w->animation_progress,
®_bound, ®_visible,
®_paint, ®_paint_in_bound);
}
} else {
process_window_for_painting(ps, w, w->win_image,
1,
®_bound, ®_visible,
®_paint, ®_paint_in_bound);
}
}
skip:
pixman_region32_fini(®_bound);
pixman_region32_fini(®_paint_in_bound);
}
pixman_region32_fini(®_paint);
pixman_region32_fini(®_shadow_clip);
if (ps->o.monitor_repaint) {
const struct color DEBUG_COLOR = {0.5, 0, 0, 0.5};
auto reg_damage_debug = get_damage(ps, false);
ps->backend_data->ops->fill(ps->backend_data, DEBUG_COLOR, ®_damage_debug);
pixman_region32_fini(®_damage_debug);
}
// Move the head of the damage ring
ps->damage = ps->damage - 1;
if (ps->damage < ps->damage_ring) {
ps->damage = ps->damage_ring + ps->ndamage - 1;
}
pixman_region32_clear(ps->damage);
if (ps->backend_data->ops->present) {
// Present the rendered scene
// Vsync is done here
ps->backend_data->ops->present(ps->backend_data, ®_damage);
}
pixman_region32_fini(®_damage);
#ifdef DEBUG_REPAINT
struct timespec now = get_time_timespec();
struct timespec diff = {0};
timespec_subtract(&diff, &now, &last_paint);
log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
last_paint = now;
log_trace("paint:");
for (win *w = t; w; w = w->prev_trans)
log_trace(" %#010lx", w->id);
#endif
}
// vim: set noet sw=8 ts=8 :
|