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(&region);
42 	if (buffer_age == -1 || buffer_age > ps->ndamage) {
43 		pixman_region32_copy(&region, &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(&region, &region, &ps->damage_ring[curr]);
50 		}
51 		pixman_region32_intersect(&region, &region, &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(&reg_damage);
76 		pixman_region32_copy(&reg_damage, &ps->screen_reg);
77 	}
78 
79 	if (!pixman_region32_not_empty(&reg_damage)) {
80 		pixman_region32_fini(&reg_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(&reg_damage, blur_width * resize_factor,
122 		                       blur_height * resize_factor);
123 		reg_paint = resize_region(&reg_damage, blur_width * resize_factor,
124 		                          blur_height * resize_factor);
125 		pixman_region32_intersect(&reg_paint, &reg_paint, &ps->screen_reg);
126 		pixman_region32_intersect(&reg_damage, &reg_damage, &ps->screen_reg);
127 	} else {
128 		pixman_region32_init(&reg_paint);
129 		pixman_region32_copy(&reg_paint, &reg_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(&reg_visible);
136 	pixman_region32_copy(&reg_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(&reg_visible, &reg_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, &reg_paint);
151 	}
152 
153 	if (ps->root_image) {
154 		ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0,
155 		                               &reg_paint, &reg_visible);
156 	} else {
157 		ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
158 		                            &reg_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(&reg_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(&reg_paint_in_bound);
180 		pixman_region32_intersect(&reg_paint_in_bound, &reg_bound, &reg_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(&reg_paint_in_bound,
190 			                          &reg_paint_in_bound, &reg_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 				    &reg_paint_in_bound, &reg_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(&reg_blur, w->g.x, w->g.y);
254 				// make sure reg_blur \in reg_paint
255 				pixman_region32_intersect(&reg_blur, &reg_blur, &reg_paint);
256 				if (ps->o.transparent_clipping) {
257 					// ref: <transparent-clipping-note>
258 					pixman_region32_intersect(&reg_blur, &reg_blur,
259 					                          &reg_visible);
260 				}
261 				ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
262 				                            ps->backend_blur_context,
263 				                            &reg_blur, &reg_visible);
264 				pixman_region32_fini(&reg_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(&reg_shadow, &reg_shadow, &reg_paint);
275 			if (!ps->o.wintype_option[w->window_type].full_shadow) {
276 				pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_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(&reg_shadow, &reg_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 				    &reg_shadow, &reg_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(&reg_shadow, &reg_shadow,
302 				                          &reg_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, &reg_shadow, &reg_visible);
310 			} else {
311 				auto new_img = ps->backend_data->ops->copy(
312 				    ps->backend_data, w->shadow_image, &reg_visible);
313 				ps->backend_data->ops->image_op(
314 				    ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img,
315 				    NULL, &reg_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, &reg_shadow, &reg_visible);
319 				ps->backend_data->ops->release_image(ps->backend_data, new_img);
320 			}
321 			pixman_region32_fini(&reg_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 			    &reg_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 			                               &reg_paint_in_bound, &reg_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(&reg_bound_local);
347 			pixman_region32_copy(&reg_bound_local, &reg_bound);
348 			pixman_region32_translate(&reg_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(&reg_visible_local);
356 			pixman_region32_intersect(&reg_visible_local, &reg_visible, &reg_paint);
357 			pixman_region32_translate(&reg_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(&reg_visible_local, &reg_visible_local,
363 			                          &reg_bound_local);
364 
365 			auto new_img = ps->backend_data->ops->copy(
366 			    ps->backend_data, w->win_image, &reg_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, &reg_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 				    &reg_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, &reg_frame,
385 				    &reg_visible_local, (double[]){w->frame_opacity});
386 				pixman_region32_fini(&reg_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, &reg_visible_local, (double[]){w->opacity});
392 			}
393 			ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x,
394 			                               w->g.y, &reg_paint_in_bound,
395 			                               &reg_visible);
396 			ps->backend_data->ops->release_image(ps->backend_data, new_img);
397 			pixman_region32_fini(&reg_visible_local);
398 			pixman_region32_fini(&reg_bound_local);
399 		}
400 		pixman_region32_fini(&reg_bound);
401 		pixman_region32_fini(&reg_paint_in_bound);
402 	}
403 	pixman_region32_fini(&reg_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}, &reg_damage_debug);
409 		pixman_region32_fini(&reg_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, &reg_damage);
423 	}
424 
425 	pixman_region32_fini(&reg_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