1 // SPDX-License-Identifier: MPL-2.0
2 // Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
3 #include <xcb/sync.h>
4 #include <xcb/xcb.h>
5
6 #include "backend/backend.h"
7 #include "common.h"
8 #include "compiler.h"
9 #include "config.h"
10 #include "log.h"
11 #include "region.h"
12 #include "types.h"
13 #include "win.h"
14 #include "x.h"
15
16 extern struct backend_operations xrender_ops, dummy_ops;
17 #ifdef CONFIG_OPENGL
18 extern struct backend_operations glx_ops;
19 #endif
20
21 struct backend_operations *backend_list[NUM_BKEND] = {
22 [BKEND_XRENDER] = &xrender_ops,
23 [BKEND_DUMMY] = &dummy_ops,
24 #ifdef CONFIG_OPENGL
25 [BKEND_GLX] = &glx_ops,
26 #endif
27 };
28
29 /**
30 * @param all_damage if true ignore damage and repaint the whole screen
31 */
get_damage(session_t * ps,bool all_damage)32 region_t get_damage(session_t *ps, bool all_damage) {
33 region_t region;
34 auto buffer_age_fn = ps->backend_data->ops->buffer_age;
35 int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1;
36
37 if (all_damage) {
38 buffer_age = -1;
39 }
40
41 pixman_region32_init(®ion);
42 if (buffer_age == -1 || buffer_age > ps->ndamage) {
43 pixman_region32_copy(®ion, &ps->screen_reg);
44 } else {
45 for (int i = 0; i < buffer_age; i++) {
46 auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
47 log_trace("damage index: %d, damage ring offset: %ld", i, curr);
48 dump_region(&ps->damage_ring[curr]);
49 pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]);
50 }
51 pixman_region32_intersect(®ion, ®ion, &ps->screen_reg);
52 }
53 return region;
54 }
55
56 /// paint all windows
paint_all_new(session_t * ps,struct managed_win * t,bool ignore_damage)57 void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
58 if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) {
59 if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
60 log_error("x_fence_sync failed, xrender-sync-fence will be "
61 "disabled from now on.");
62 xcb_sync_destroy_fence(ps->c, ps->sync_fence);
63 ps->sync_fence = XCB_NONE;
64 ps->o.xrender_sync_fence = false;
65 ps->xsync_exists = false;
66 }
67 }
68 // All painting will be limited to the damage, if _some_ of
69 // the paints bleed out of the damage region, it will destroy
70 // part of the image we want to reuse
71 region_t reg_damage;
72 if (!ignore_damage) {
73 reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
74 } else {
75 pixman_region32_init(®_damage);
76 pixman_region32_copy(®_damage, &ps->screen_reg);
77 }
78
79 if (!pixman_region32_not_empty(®_damage)) {
80 pixman_region32_fini(®_damage);
81 return;
82 }
83
84 #ifdef DEBUG_REPAINT
85 static struct timespec last_paint = {0};
86 #endif
87
88 // <damage-note>
89 // If use_damage is enabled, we MUST make sure only the damaged regions of the
90 // screen are ever touched by the compositor. The reason is that at the beginning
91 // of each render, we clear the damaged regions with the wallpaper, and nothing
92 // else. If later during the render we changed anything outside the damaged
93 // region, that won't be cleared by the next render, and will thus accumulate.
94 // (e.g. if shadow is drawn outside the damaged region, it will become thicker and
95 // thicker over time.)
96
97 /// The adjusted damaged regions
98 region_t reg_paint;
99 assert(ps->o.blur_method != BLUR_METHOD_INVALID);
100 if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) {
101 int blur_width, blur_height;
102 ps->backend_data->ops->get_blur_size(ps->backend_blur_context,
103 &blur_width, &blur_height);
104
105 // The region of screen a given window influences will be smeared
106 // out by blur. With more windows on top of the given window, the
107 // influences region will be smeared out more.
108 //
109 // Also, blurring requires data slightly outside the area that needs
110 // to be blurred. The more semi-transparent windows are stacked on top
111 // of each other, the larger the area will be.
112 //
113 // Instead of accurately calculate how much bigger the damage
114 // region will be because of blur, we assume the worst case here.
115 // That is, the damaged window is at the bottom of the stack, and
116 // all other windows have semi-transparent background
117 int resize_factor = 1;
118 if (t) {
119 resize_factor = t->stacking_rank;
120 }
121 resize_region_in_place(®_damage, blur_width * resize_factor,
122 blur_height * resize_factor);
123 reg_paint = resize_region(®_damage, blur_width * resize_factor,
124 blur_height * resize_factor);
125 pixman_region32_intersect(®_paint, ®_paint, &ps->screen_reg);
126 pixman_region32_intersect(®_damage, ®_damage, &ps->screen_reg);
127 } else {
128 pixman_region32_init(®_paint);
129 pixman_region32_copy(®_paint, ®_damage);
130 }
131
132 // A hint to backend, the region that will be visible on screen
133 // backend can optimize based on this info
134 region_t reg_visible;
135 pixman_region32_init(®_visible);
136 pixman_region32_copy(®_visible, &ps->screen_reg);
137 if (t && !ps->o.transparent_clipping) {
138 // Calculate the region upon which the root window (wallpaper) is to be
139 // painted based on the ignore region of the lowest window, if available
140 //
141 // NOTE If transparent_clipping is enabled, transparent windows are
142 // included in the reg_ignore, but we still want to have the wallpaper
143 // beneath them, so we don't use reg_ignore for wallpaper in that case.
144 pixman_region32_subtract(®_visible, ®_visible, t->reg_ignore);
145 }
146
147 // TODO Bind root pixmap
148
149 if (ps->backend_data->ops->prepare) {
150 ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
151 }
152
153 if (ps->root_image) {
154 ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0,
155 ®_paint, ®_visible);
156 } else {
157 ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
158 ®_paint);
159 }
160
161 // Windows are sorted from bottom to top
162 // Each window has a reg_ignore, which is the region obscured by all the windows
163 // on top of that window. This is used to reduce the number of pixels painted.
164 //
165 // Whether this is beneficial is to be determined XXX
166 for (auto w = t; w; w = w->prev_trans) {
167 pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore);
168 assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
169 assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
170 assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE));
171
172 // The bounding shape of the window, in global/target coordinates
173 // reminder: bounding shape contains the WM frame
174 auto reg_bound = win_get_bounding_shape_global_by_val(w);
175
176 // The clip region for the current window, in global/target coordinates
177 // reg_paint_in_bound \in reg_paint
178 region_t reg_paint_in_bound;
179 pixman_region32_init(®_paint_in_bound);
180 pixman_region32_intersect(®_paint_in_bound, ®_bound, ®_paint);
181 if (ps->o.transparent_clipping) {
182 // <transparent-clipping-note>
183 // If transparent_clipping is enabled, we need to be SURE that
184 // things are not drawn inside reg_ignore, because otherwise they
185 // will appear underneath transparent windows.
186 // So here we have make sure reg_paint_in_bound \in reg_visible
187 // There are a few other places below where this is needed as
188 // well.
189 pixman_region32_intersect(®_paint_in_bound,
190 ®_paint_in_bound, ®_visible);
191 }
192
193 // Blur window background
194 // TODO since the background might change the content of the window (e.g.
195 // with shaders), we should consult the background whether the window
196 // is transparent or not. for now we will just rely on the
197 // force_win_blend option
198 auto real_win_mode = w->mode;
199
200 if (w->blur_background &&
201 (ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
202 (ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
203 // Minimize the region we try to blur, if the window
204 // itself is not opaque, only the frame is.
205
206 double blur_opacity = 1;
207 if (w->opacity < (1.0 / MAX_ALPHA)) {
208 // Hide blur for fully transparent windows.
209 blur_opacity = 0;
210 } else if (w->state == WSTATE_MAPPING) {
211 // Gradually increase the blur intensity during
212 // fading in.
213 assert(w->opacity <= w->opacity_target);
214 blur_opacity = w->opacity / w->opacity_target;
215 } else if (w->state == WSTATE_UNMAPPING ||
216 w->state == WSTATE_DESTROYING) {
217 // Gradually decrease the blur intensity during
218 // fading out.
219 assert(w->opacity <= w->opacity_target_old);
220 blur_opacity = w->opacity / w->opacity_target_old;
221 } else if (w->state == WSTATE_FADING) {
222 if (w->opacity < w->opacity_target &&
223 w->opacity_target_old < (1.0 / MAX_ALPHA)) {
224 // Gradually increase the blur intensity during
225 // fading in.
226 assert(w->opacity <= w->opacity_target);
227 blur_opacity = w->opacity / w->opacity_target;
228 } else if (w->opacity > w->opacity_target &&
229 w->opacity_target < (1.0 / MAX_ALPHA)) {
230 // Gradually decrease the blur intensity during
231 // fading out.
232 assert(w->opacity <= w->opacity_target_old);
233 blur_opacity = w->opacity / w->opacity_target_old;
234 }
235 }
236 assert(blur_opacity >= 0 && blur_opacity <= 1);
237
238 if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
239 // We need to blur the bounding shape of the window
240 // (reg_paint_in_bound = reg_bound \cap reg_paint)
241 ps->backend_data->ops->blur(
242 ps->backend_data, blur_opacity, ps->backend_blur_context,
243 ®_paint_in_bound, ®_visible);
244 } else {
245 // Window itself is solid, we only need to blur the frame
246 // region
247
248 // Readability assertions
249 assert(ps->o.blur_background_frame);
250 assert(real_win_mode == WMODE_FRAME_TRANS);
251
252 auto reg_blur = win_get_region_frame_local_by_val(w);
253 pixman_region32_translate(®_blur, w->g.x, w->g.y);
254 // make sure reg_blur \in reg_paint
255 pixman_region32_intersect(®_blur, ®_blur, ®_paint);
256 if (ps->o.transparent_clipping) {
257 // ref: <transparent-clipping-note>
258 pixman_region32_intersect(®_blur, ®_blur,
259 ®_visible);
260 }
261 ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
262 ps->backend_blur_context,
263 ®_blur, ®_visible);
264 pixman_region32_fini(®_blur);
265 }
266 }
267
268 // Draw shadow on target
269 if (w->shadow) {
270 assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
271 // Clip region for the shadow
272 // reg_shadow \in reg_paint
273 auto reg_shadow = win_extents_by_val(w);
274 pixman_region32_intersect(®_shadow, ®_shadow, ®_paint);
275 if (!ps->o.wintype_option[w->window_type].full_shadow) {
276 pixman_region32_subtract(®_shadow, ®_shadow, ®_bound);
277 }
278
279 // Mask out the region we don't want shadow on
280 if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
281 pixman_region32_subtract(®_shadow, ®_shadow,
282 &ps->shadow_exclude_reg);
283 }
284
285 if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
286 w->xinerama_scr < ps->xinerama_nscrs) {
287 // There can be a window where number of screens is
288 // updated, but the screen number attached to the windows
289 // have not.
290 //
291 // Window screen number will be updated eventually, so
292 // here we just check to make sure we don't access out of
293 // bounds.
294 pixman_region32_intersect(
295 ®_shadow, ®_shadow,
296 &ps->xinerama_scr_regs[w->xinerama_scr]);
297 }
298
299 if (ps->o.transparent_clipping) {
300 // ref: <transparent-clipping-note>
301 pixman_region32_intersect(®_shadow, ®_shadow,
302 ®_visible);
303 }
304
305 assert(w->shadow_image);
306 if (w->opacity == 1) {
307 ps->backend_data->ops->compose(
308 ps->backend_data, w->shadow_image, w->g.x + w->shadow_dx,
309 w->g.y + w->shadow_dy, ®_shadow, ®_visible);
310 } else {
311 auto new_img = ps->backend_data->ops->copy(
312 ps->backend_data, w->shadow_image, ®_visible);
313 ps->backend_data->ops->image_op(
314 ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img,
315 NULL, ®_visible, (double[]){w->opacity});
316 ps->backend_data->ops->compose(
317 ps->backend_data, new_img, w->g.x + w->shadow_dx,
318 w->g.y + w->shadow_dy, ®_shadow, ®_visible);
319 ps->backend_data->ops->release_image(ps->backend_data, new_img);
320 }
321 pixman_region32_fini(®_shadow);
322 }
323
324 // Set max brightness
325 if (ps->o.max_brightness < 1.0) {
326 ps->backend_data->ops->image_op(
327 ps->backend_data, IMAGE_OP_MAX_BRIGHTNESS, w->win_image, NULL,
328 ®_visible, &ps->o.max_brightness);
329 }
330
331 // Draw window on target
332 if (!w->invert_color && !w->dim && w->frame_opacity == 1 && w->opacity == 1) {
333 ps->backend_data->ops->compose(ps->backend_data, w->win_image,
334 w->g.x, w->g.y,
335 ®_paint_in_bound, ®_visible);
336 } else if (w->opacity * MAX_ALPHA >= 1) {
337 // We don't need to paint the window body itself if it's
338 // completely transparent.
339
340 // For window image processing, we don't have to limit the process
341 // region to damage for correctness. (see <damager-note> for
342 // details)
343
344 // The bounding shape, in window local coordinates
345 region_t reg_bound_local;
346 pixman_region32_init(®_bound_local);
347 pixman_region32_copy(®_bound_local, ®_bound);
348 pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
349
350 // The visible region, in window local coordinates
351 // Although we don't limit process region to damage, we provide
352 // that info in reg_visible as a hint. Since window image data
353 // outside of the damage region won't be painted onto target
354 region_t reg_visible_local;
355 pixman_region32_init(®_visible_local);
356 pixman_region32_intersect(®_visible_local, ®_visible, ®_paint);
357 pixman_region32_translate(®_visible_local, -w->g.x, -w->g.y);
358 // Data outside of the bounding shape won't be visible, but it is
359 // not necessary to limit the image operations to the bounding
360 // shape yet. So pass that as the visible region, not the clip
361 // region.
362 pixman_region32_intersect(®_visible_local, ®_visible_local,
363 ®_bound_local);
364
365 auto new_img = ps->backend_data->ops->copy(
366 ps->backend_data, w->win_image, ®_visible_local);
367 if (w->invert_color) {
368 ps->backend_data->ops->image_op(
369 ps->backend_data, IMAGE_OP_INVERT_COLOR_ALL, new_img,
370 NULL, ®_visible_local, NULL);
371 }
372 if (w->dim) {
373 double dim_opacity = ps->o.inactive_dim;
374 if (!ps->o.inactive_dim_fixed) {
375 dim_opacity *= w->opacity;
376 }
377 ps->backend_data->ops->image_op(
378 ps->backend_data, IMAGE_OP_DIM_ALL, new_img, NULL,
379 ®_visible_local, (double[]){dim_opacity});
380 }
381 if (w->frame_opacity != 1) {
382 auto reg_frame = win_get_region_frame_local_by_val(w);
383 ps->backend_data->ops->image_op(
384 ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame,
385 ®_visible_local, (double[]){w->frame_opacity});
386 pixman_region32_fini(®_frame);
387 }
388 if (w->opacity != 1) {
389 ps->backend_data->ops->image_op(
390 ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img,
391 NULL, ®_visible_local, (double[]){w->opacity});
392 }
393 ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x,
394 w->g.y, ®_paint_in_bound,
395 ®_visible);
396 ps->backend_data->ops->release_image(ps->backend_data, new_img);
397 pixman_region32_fini(®_visible_local);
398 pixman_region32_fini(®_bound_local);
399 }
400 pixman_region32_fini(®_bound);
401 pixman_region32_fini(®_paint_in_bound);
402 }
403 pixman_region32_fini(®_paint);
404
405 if (ps->o.monitor_repaint) {
406 auto reg_damage_debug = get_damage(ps, false);
407 ps->backend_data->ops->fill(
408 ps->backend_data, (struct color){0.5, 0, 0, 0.5}, ®_damage_debug);
409 pixman_region32_fini(®_damage_debug);
410 }
411
412 // Move the head of the damage ring
413 ps->damage = ps->damage - 1;
414 if (ps->damage < ps->damage_ring) {
415 ps->damage = ps->damage_ring + ps->ndamage - 1;
416 }
417 pixman_region32_clear(ps->damage);
418
419 if (ps->backend_data->ops->present) {
420 // Present the rendered scene
421 // Vsync is done here
422 ps->backend_data->ops->present(ps->backend_data, ®_damage);
423 }
424
425 pixman_region32_fini(®_damage);
426
427 #ifdef DEBUG_REPAINT
428 struct timespec now = get_time_timespec();
429 struct timespec diff = {0};
430 timespec_subtract(&diff, &now, &last_paint);
431 log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
432 last_paint = now;
433 log_trace("paint:");
434 for (win *w = t; w; w = w->prev_trans)
435 log_trace(" %#010lx", w->id);
436 #endif
437 }
438
439 // vim: set noet sw=8 ts=8 :
440