-
-
Notifications
You must be signed in to change notification settings - Fork 598
/
Copy pathpicom.c
2850 lines (2521 loc) · 87.8 KB
/
picom.c
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
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: MIT
/*
* picom - a compositor for X11
*
* Based on `compton` - Copyright (c) 2011-2013, Christopher Jeffrey
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2019-2023, Yuxuan Shui
*
* See LICENSE-mit for more information.
*
*/
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <ev.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <math.h>
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/glx.h>
#include <xcb/present.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb_aux.h>
#include <xcb/xfixes.h>
#include <picom/types.h>
#include <test.h>
#ifdef CONFIG_OPENGL
#include "opengl.h"
#endif
#include "api_internal.h"
#include "atom.h"
#include "backend/backend.h"
#include "c2.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "dbus.h"
#include "diagnostic.h"
#include "event.h"
#include "inspect.h"
#include "log.h"
#include "options.h"
#include "picom.h"
#include "region.h"
#include "render.h"
#include "renderer/command_builder.h"
#include "renderer/layout.h"
#include "renderer/renderer.h"
#include "utils/dynarr.h"
#include "utils/file_watch.h"
#include "utils/kernel.h"
#include "utils/list.h"
#include "utils/misc.h"
#include "utils/statistics.h"
#include "utils/uthash_extra.h"
#include "vblank.h"
#include "wm/defs.h"
#include "wm/wm.h"
#include "x.h"
/// Get session_t pointer from a pointer to a member of session_t
#define session_ptr(ptr, member) \
({ \
const __typeof__(((session_t *)0)->member) *__mptr = (ptr); \
(session_t *)((char *)__mptr - offsetof(session_t, member)); \
})
static bool must_use redirect_start(session_t *ps);
static void unredirect(session_t *ps);
// === Global constants ===
/// Name strings for window types.
const struct wintype_info WINTYPES[] = {
[WINTYPE_UNKNOWN] = {"unknown", NULL},
#define X(name, type) [WINTYPE_##type] = {#name, "_NET_WM_WINDOW_TYPE_" #type}
X(desktop, DESKTOP),
X(dock, DOCK),
X(toolbar, TOOLBAR),
X(menu, MENU),
X(utility, UTILITY),
X(splash, SPLASH),
X(dialog, DIALOG),
X(normal, NORMAL),
X(dropdown_menu, DROPDOWN_MENU),
X(popup_menu, POPUP_MENU),
X(tooltip, TOOLTIP),
X(notification, NOTIFICATION),
X(combo, COMBO),
X(dnd, DND),
#undef X
};
// clang-format off
/// Names of backends.
const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender",
[BKEND_GLX] = "glx",
[BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid",
[BKEND_DUMMY] = "dummy",
[BKEND_EGL] = "egl",
NULL};
// clang-format on
// === Global variables ===
/// Pointer to current session, as a global variable. Only used by
/// xerror(), which could not have a pointer to current session passed in.
/// XXX Limit what xerror can access by not having this pointer
session_t *ps_g = NULL;
void quit(session_t *ps) {
ps->quit = true;
ev_break(ps->loop, EVBREAK_ALL);
}
/**
* Convert struct timespec to milliseconds.
*/
static inline int64_t timespec_ms(struct timespec ts) {
return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
}
enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unused, void *ud) {
auto ps = (session_t *)ud;
if (!ps->backend_busy) {
return VBLANK_CALLBACK_DONE;
}
struct timespec render_time;
bool completed =
ps->backend_data->ops.last_render_time(ps->backend_data, &render_time);
if (!completed) {
// Render hasn't completed yet, we can't start another render.
// Check again at the next vblank.
log_debug("Last render did not complete during vblank, msc: "
"%" PRIu64,
ps->last_msc);
return VBLANK_CALLBACK_AGAIN;
}
// The frame has been finished and presented, record its render time.
if (global_debug_options.smart_frame_pacing) {
int render_time_us =
(int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L);
render_statistics_add_render_time_sample(
&ps->render_stats, render_time_us + (int)ps->last_schedule_delay);
log_verbose("Last render call took: %d (gpu) + %d (cpu) us, "
"last_msc: %" PRIu64,
render_time_us, (int)ps->last_schedule_delay, ps->last_msc);
}
ps->backend_busy = false;
return VBLANK_CALLBACK_DONE;
}
enum vblank_callback_action
collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
double vblank_interval = NAN;
assert(ps->frame_pacing);
assert(ps->vblank_scheduler);
if (!global_debug_options.smart_frame_pacing) {
// We don't need to collect statistics if we are not doing smart frame
// pacing.
return VBLANK_CALLBACK_DONE;
}
// TODO(yshui): this naive method of estimating vblank interval does not handle
// the variable refresh rate case very well. This includes the case
// of a VRR enabled monitor; or a monitor that's turned off, in which
// case the vblank events might slow down or stop all together.
// I tried using DPMS to detect monitor power state, and stop adding
// samples when the monitor is off, but I had a hard time to get it
// working reliably, there are just too many corner cases.
// Don't add sample again if we already collected statistics for this vblank
if (ps->last_msc < e->msc) {
if (ps->last_msc_instant != 0) {
auto frame_count = e->msc - ps->last_msc;
auto frame_time =
(int)((e->ust - ps->last_msc_instant) / frame_count);
if (frame_count == 1) {
render_statistics_add_vblank_time_sample(
&ps->render_stats, frame_time);
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"ust: "
"%" PRIu64,
frame_count, frame_time, e->ust);
} else {
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"msc: "
"%" PRIu64 ", not adding sample.",
frame_count, frame_time, e->ust);
}
}
ps->last_msc_instant = e->ust;
ps->last_msc = e->msc;
} else if (ps->last_msc > e->msc) {
log_warn("PresentCompleteNotify msc is going backwards, last_msc: "
"%" PRIu64 ", current msc: %" PRIu64,
ps->last_msc, e->msc);
ps->last_msc_instant = 0;
ps->last_msc = 0;
}
vblank_interval = render_statistics_get_vblank_time(&ps->render_stats);
log_trace("Vblank interval estimate: %f us", vblank_interval);
if (vblank_interval == 0) {
// We don't have enough data for vblank interval estimate, schedule
// another vblank event.
return VBLANK_CALLBACK_AGAIN;
}
return VBLANK_CALLBACK_DONE;
}
void schedule_render(session_t *ps, bool triggered_by_vblank);
/// vblank callback scheduled by schedule_render, when a render is ongoing.
///
/// Check if previously queued render has finished, and reschedule render if it has.
enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
assert(ps->frame_pacing);
assert(ps->render_queued);
assert(ps->vblank_scheduler);
log_verbose("Rescheduling render at vblank, msc: %" PRIu64, e->msc);
collect_vblank_interval_statistics(e, ud);
check_render_finish(e, ud);
if (ps->backend_busy) {
return VBLANK_CALLBACK_AGAIN;
}
schedule_render(ps, false);
return VBLANK_CALLBACK_DONE;
}
/// How many seconds into the future should we start rendering the next frame.
///
/// Renders are scheduled like this:
///
/// 1. queue_redraw() queues a new render by calling schedule_render, if there
/// is no render currently scheduled. i.e. render_queued == false.
/// 2. then, we need to figure out the best time to start rendering. we need to
/// at least know when the next vblank will start, as we can't start render
/// before the current rendered frame is displayed on screen. we have this
/// information from the vblank scheduler, it will notify us when that happens.
/// we might also want to delay the rendering even further to reduce latency,
/// this is discussed below, in FUTURE WORKS.
/// 3. we schedule a render for that target point in time.
/// 4. draw_callback() is called at the schedule time (i.e. when scheduled
/// vblank event is delivered). Backend APIs are called to issue render
/// commands. render_queued is set to false, and backend_busy is set to true.
///
/// There are some considerations in step 2:
///
/// First of all, a vblank event being delivered
/// doesn't necessarily mean the frame has been displayed on screen. If a frame
/// takes too long to render, it might miss the current vblank, and will be
/// displayed on screen during one of the subsequent vblanks. So in
/// schedule_render_at_vblank, we ask the backend to see if it has finished
/// rendering. if not, render_queued is unchanged, and another vblank is
/// scheduled; otherwise, draw_callback_impl will be scheduled to be call at
/// an appropriate time. Second, we might not have rendered for the previous vblank,
/// in which case the last vblank event we received could be many frames in the past,
/// so we can't make scheduling decisions based on that. So we always schedule
/// a vblank event when render is queued, and make scheduling decisions when the
/// event is delivered.
///
/// All of the above is what happens when frame_pacing is true. Otherwise
/// render_in_progress is either QUEUED or IDLE, and queue_redraw will always
/// schedule a render to be started immediately. PresentCompleteNotify will not
/// be received, and handle_end_of_vblank will not be called.
///
/// The `triggered_by_timer` parameter is used to indicate whether this function
/// is triggered by a steady timer, i.e. we are rendering for each vblank. The
/// other case is when we stop rendering for a while because there is no changes
/// on screen, then something changed and schedule_render is triggered by a
/// DamageNotify. The idea is that when the schedule is triggered by a steady
/// timer, schedule_render will be called at a predictable offset into each
/// vblank.
///
/// # FUTURE WORKS
///
/// As discussed in step 2 above, we might want to delay the rendering even
/// further. If we know the time it takes to render a frame, and the interval
/// between vblanks, we can try to schedule the render to start at a point in
/// time that's closer to the next vblank. We should be able to get this
/// information by doing statistics on the render time of previous frames, which
/// is available from the backends; and the interval between vblank events,
/// which is available from the vblank scheduler.
///
/// The code that does this is already implemented below, but disabled by
/// default. There are several problems with it, see bug #1072.
void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) {
// If the backend is busy, we will try again at the next vblank.
if (ps->backend_busy) {
// We should never have set backend_busy to true unless frame_pacing is
// enabled.
assert(ps->vblank_scheduler);
assert(ps->frame_pacing);
log_verbose("Backend busy, will reschedule render at next vblank.");
if (!vblank_scheduler_schedule(ps->vblank_scheduler,
reschedule_render_at_vblank, ps)) {
// TODO(yshui): handle error here
abort();
}
return;
}
// By default, we want to schedule render immediately, later in this function we
// might adjust that and move the render later, based on render timing statistics.
double delay_s = 0;
unsigned int divisor = 0;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000;
ps->next_render = now_us;
if (!ps->frame_pacing || !ps->redirected) {
// If not doing frame pacing, schedule a render immediately; if
// not redirected, we schedule immediately to have a chance to
// redirect. We won't have frame or render timing information
// anyway.
assert(!ev_is_active(&ps->draw_timer));
goto schedule;
}
// if global_debug_options.smart_frame_pacing is false, we won't have any render
// time or vblank interval estimates, so we would naturally fallback to schedule
// render immediately.
auto render_budget = render_statistics_get_budget(&ps->render_stats);
auto frame_time = render_statistics_get_vblank_time(&ps->render_stats);
if (frame_time == 0) {
// We don't have enough data for render time estimates, maybe there's
// no frame rendered yet, or the backend doesn't support render timing
// information, schedule render immediately.
log_verbose("Not enough data for render time estimates.");
goto schedule;
}
if (render_budget >= frame_time) {
// If the estimated render time is already longer than the estimated
// vblank interval, there is no way we can make it. Instead of always
// dropping frames, we try desperately to catch up and schedule a
// render immediately.
log_verbose("Render budget: %u us >= frame time: %" PRIu32 " us",
render_budget, frame_time);
goto schedule;
}
auto target_frame = (now_us + render_budget - ps->last_msc_instant) / frame_time + 1;
auto const deadline = ps->last_msc_instant + target_frame * frame_time;
unsigned int available = 0;
if (deadline > now_us) {
available = (unsigned int)(deadline - now_us);
}
if (available > render_budget) {
delay_s = (double)(available - render_budget) / 1000000.0;
ps->next_render = deadline - render_budget;
}
if (delay_s > 1) {
log_warn("Delay too long: %f s, render_budget: %d us, frame_time: "
"%" PRIu32 " us, now_us: %" PRIu64 " us, next_msc: %" PRIu64 " u"
"s",
delay_s, render_budget, frame_time, now_us, deadline);
}
log_verbose("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, "
"frame_time: %" PRIu32 ", now_us: %" PRIu64 ", next_render: %" PRIu64
", next_msc: %" PRIu64 ", divisor: "
"%d",
delay_s, ps->last_msc_instant, render_budget, frame_time, now_us,
ps->next_render, deadline, divisor);
schedule:
// If the backend is not busy, we just need to schedule the render at the
// specified time; otherwise we need to wait for the next vblank event and
// reschedule.
ps->last_schedule_delay = 0;
assert(!ev_is_active(&ps->draw_timer));
ev_timer_set(&ps->draw_timer, delay_s, 0);
ev_timer_start(ps->loop, &ps->draw_timer);
}
void queue_redraw(session_t *ps) {
log_verbose("Queue redraw, render_queued: %d, backend_busy: %d",
ps->render_queued, ps->backend_busy);
if (ps->render_queued) {
return;
}
ps->render_queued = true;
schedule_render(ps, false);
}
/**
* Get a region of the screen size.
*/
static inline void get_screen_region(session_t *ps, region_t *res) {
pixman_box32_t b = {.x1 = 0, .y1 = 0, .x2 = ps->root_width, .y2 = ps->root_height};
pixman_region32_fini(res);
pixman_region32_init_rects(res, &b, 1);
}
void add_damage(session_t *ps, const region_t *damage) {
// Ignore damage when screen isn't redirected
if (!ps->redirected) {
return;
}
if (!damage || ps->damage_ring.count <= 0) {
return;
}
log_trace("Adding damage: ");
dump_region(damage);
auto cursor = &ps->damage_ring.damages[ps->damage_ring.cursor];
pixman_region32_union(cursor, cursor, (region_t *)damage);
}
// === Windows ===
/**
* Rebuild cached <code>screen_reg</code>.
*/
static void rebuild_screen_reg(session_t *ps) {
get_screen_region(ps, &ps->screen_reg);
}
/// Free up all the images and deinit the backend
static void destroy_backend(session_t *ps) {
wm_stack_foreach_safe(ps->wm, cursor, next_cursor) {
auto w = wm_ref_deref(cursor);
if (w == NULL) {
continue;
}
// An unmapped window shouldn't have a pixmap, unless it has animation
// running. (`w->previous.state != w->state` means there might be
// animation but we haven't had a chance to start it because
// `win_process_animation_and_state_change` hasn't been called.)
// TBH, this assertion is probably too complex than what it's worth.
assert(!w->win_image || w->state == WSTATE_MAPPED ||
w->running_animation_instance != NULL || w->previous.state != w->state);
// Wrapping up animation in progress
free(w->running_animation_instance);
w->running_animation_instance = NULL;
if (ps->backend_data) {
// Unmapped windows could still have shadow images.
// In some cases, the window might have PIXMAP_STALE flag set:
// 1. If the window is unmapped. Their stale flags won't be
// handled until they are mapped.
// 2. If we haven't had chance to handle the stale flags. This
// could happen if we received a root ConfigureNotify
// _immidiately_ after we redirected.
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
win_release_images(ps->backend_data, w);
}
free_paint(ps, &w->paint);
if (w->state == WSTATE_DESTROYED) {
win_destroy_finish(ps, w);
}
}
HASH_ITER2(ps->shaders, shader) {
if (shader->backend_shader != NULL) {
ps->backend_data->ops.destroy_shader(ps->backend_data,
shader->backend_shader);
shader->backend_shader = NULL;
}
}
if (ps->backend_data && ps->root_image) {
ps->backend_data->ops.release_image(ps->backend_data, ps->root_image);
ps->root_image = NULL;
}
if (ps->backend_data) {
if (ps->renderer) {
renderer_free(ps->backend_data, ps->renderer);
ps->renderer = NULL;
}
// deinit backend
if (ps->backend_blur_context) {
ps->backend_data->ops.destroy_blur_context(
ps->backend_data, ps->backend_blur_context);
ps->backend_blur_context = NULL;
}
ps->backend_data->ops.deinit(ps->backend_data);
ps->backend_data = NULL;
}
}
static bool initialize_blur(session_t *ps) {
struct kernel_blur_args kargs;
struct gaussian_blur_args gargs;
struct box_blur_args bargs;
struct dual_kawase_blur_args dkargs;
void *args = NULL;
switch (ps->o.blur_method) {
case BLUR_METHOD_BOX:
bargs.size = ps->o.blur_radius;
args = (void *)&bargs;
break;
case BLUR_METHOD_KERNEL:
kargs.kernel_count = ps->o.blur_kernel_count;
kargs.kernels = ps->o.blur_kerns;
args = (void *)&kargs;
break;
case BLUR_METHOD_GAUSSIAN:
gargs.size = ps->o.blur_radius;
gargs.deviation = ps->o.blur_deviation;
args = (void *)&gargs;
break;
case BLUR_METHOD_DUAL_KAWASE:
dkargs.size = ps->o.blur_radius;
dkargs.strength = ps->o.blur_strength;
args = (void *)&dkargs;
break;
default: return true;
}
enum backend_image_format format = ps->o.dithered_present
? BACKEND_IMAGE_FORMAT_PIXMAP_HIGH
: BACKEND_IMAGE_FORMAT_PIXMAP;
ps->backend_blur_context = ps->backend_data->ops.create_blur_context(
ps->backend_data, ps->o.blur_method, format, args);
return ps->backend_blur_context != NULL;
}
/// Init the backend and bind all the window pixmap to backend images
static bool initialize_backend(session_t *ps) {
if (!ps->o.use_legacy_backends) {
assert(!ps->backend_data);
// Reinitialize win_data
ps->backend_data =
backend_init(ps->o.backend, ps, session_get_target_window(ps));
api_backend_plugins_invoke(backend_name(ps->o.backend), ps->backend_data);
if (!ps->backend_data) {
log_fatal("Failed to initialize backend, aborting...");
quit(ps);
return false;
}
if (!initialize_blur(ps)) {
log_fatal("Failed to prepare for background blur, aborting...");
goto err;
}
// Create shaders
if (!ps->backend_data->ops.create_shader && ps->shaders) {
log_warn("Shaders are not supported by selected backend %s, "
"they will be ignored",
backend_name(ps->o.backend));
} else {
HASH_ITER2(ps->shaders, shader) {
assert(shader->backend_shader == NULL);
shader->backend_shader = ps->backend_data->ops.create_shader(
ps->backend_data, shader->source);
if (shader->backend_shader == NULL) {
log_warn("Failed to create shader for shader "
"file %s, this shader will not be used",
shader->key);
} else {
shader->attributes = 0;
if (ps->backend_data->ops.get_shader_attributes) {
shader->attributes =
ps->backend_data->ops.get_shader_attributes(
ps->backend_data,
shader->backend_shader);
}
log_debug("Shader %s has attributes %" PRIu64,
shader->key, shader->attributes);
}
}
}
wm_stack_foreach(ps->wm, cursor) {
auto w = wm_ref_deref(cursor);
if (w != NULL) {
assert(w->state != WSTATE_DESTROYED);
// We need to reacquire image
log_debug("Marking window %#010x (%s) for update after "
"redirection",
win_id(w), w->name);
win_set_flags(w, WIN_FLAGS_PIXMAP_STALE);
ps->pending_updates = true;
}
}
ps->renderer = renderer_new(ps->backend_data, ps->o.shadow_radius,
(struct color){.alpha = ps->o.shadow_opacity,
.red = ps->o.shadow_red,
.green = ps->o.shadow_green,
.blue = ps->o.shadow_blue},
ps->o.dithered_present);
if (!ps->renderer) {
log_fatal("Failed to create renderer, aborting...");
goto err;
}
}
// The old backends binds pixmap lazily, nothing to do here
return true;
err:
ps->backend_data->ops.deinit(ps->backend_data);
ps->backend_data = NULL;
quit(ps);
return false;
}
static inline void invalidate_reg_ignore(session_t *ps) {
// Invalidate reg_ignore from the top
wm_stack_foreach(ps->wm, cursor) {
auto top_w = wm_ref_deref(cursor);
if (top_w != NULL) {
rc_region_unref(&top_w->reg_ignore);
top_w->reg_ignore_valid = false;
break;
}
}
}
/// Handle configure event of the root window
void configure_root(session_t *ps) {
// TODO(yshui) re-initializing backend should be done outside of the
// critical section. Probably set a flag and do it in draw_callback_impl.
auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
if (!r) {
log_fatal("Failed to fetch root geometry");
abort();
}
log_info("Root configuration changed, new geometry: %dx%d", r->width, r->height);
bool has_root_change = false;
if (ps->redirected) {
// On root window changes
if (!ps->o.use_legacy_backends) {
assert(ps->backend_data);
has_root_change = ps->backend_data->ops.root_change != NULL;
} else {
// Old backend can handle root change
has_root_change = true;
}
if (!has_root_change) {
// deinit/reinit backend and free up resources if the backend
// cannot handle root change
destroy_backend(ps);
}
free_paint(ps, &ps->tgt_buffer);
}
ps->root_width = r->width;
ps->root_height = r->height;
free(r);
rebuild_screen_reg(ps);
invalidate_reg_ignore(ps);
// Whether a window is fullscreen depends on the new screen
// size. So we need to refresh the fullscreen state of all
// windows.
wm_stack_foreach(ps->wm, cursor) {
auto w = wm_ref_deref(cursor);
if (w != NULL) {
win_update_is_fullscreen(ps, w);
}
}
if (ps->redirected) {
for (int i = 0; i < ps->damage_ring.count; i++) {
pixman_region32_clear(&ps->damage_ring.damages[i]);
}
ps->damage_ring.cursor = ps->damage_ring.count - 1;
#ifdef CONFIG_OPENGL
// GLX root change callback
if (BKEND_GLX == ps->o.legacy_backend && ps->o.use_legacy_backends) {
glx_on_root_change(ps);
}
#endif
if (has_root_change) {
if (ps->backend_data != NULL) {
ps->backend_data->ops.root_change(ps->backend_data, ps);
}
// Old backend's root_change is not a specific function
} else {
if (!initialize_backend(ps)) {
log_fatal("Failed to re-initialize backend after root "
"change, aborting...");
ps->quit = true;
/* TODO(yshui) only event handlers should request
* ev_break, otherwise it's too hard to keep track of what
* can break the event loop */
ev_break(ps->loop, EVBREAK_ALL);
return;
}
// Re-acquire the root pixmap.
root_damaged(ps);
}
force_repaint(ps);
}
}
/**
* Go through the window stack and calculate some parameters for rendering.
*
* @return whether the operation succeeded
*/
static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bottom) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered
struct win *bottom = NULL;
*animation = false;
*out_bottom = NULL;
// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it.
wm_stack_foreach_safe(ps->wm, cursor, tmp) {
auto w = wm_ref_deref(cursor);
if (w == NULL) {
continue;
}
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
if (w->running_animation_instance != NULL) {
*animation = true;
}
// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->running_animation_instance != NULL) {
add_damage_from_win(ps, w);
}
if (win_has_frame(w)) {
w->frame_opacity = ps->o.frame_opacity;
} else {
w->frame_opacity = 1.0;
}
// Update window mode
w->mode = win_calc_mode(w);
// Destroy all reg_ignore above when frame opaque state changes on
// SOLID mode
if (was_painted && w->mode != mode_old) {
w->reg_ignore_valid = false;
}
}
// Opacity will not change, from now on.
rc_region_t *last_reg_ignore = rc_region_new();
bool unredir_possible = false;
// Track whether it's the highest window to paint
bool is_highest = true;
bool reg_ignore_valid = true;
wm_stack_foreach_safe(ps->wm, cursor, next_cursor) {
__label__ skip_window;
auto w = wm_ref_deref(cursor);
if (w == NULL) {
continue;
}
bool to_paint = true;
// w->to_paint remembers whether this window is painted last time
const bool was_painted = w->to_paint;
const double window_opacity = win_animatable_get(w, WIN_SCRIPT_OPACITY);
const double blur_opacity = win_animatable_get(w, WIN_SCRIPT_BLUR_OPACITY);
auto window_options = win_options(w);
if (window_options.shader->attributes & SHADER_ATTRIBUTE_ANIMATED) {
add_damage_from_win(ps, w);
*animation = true;
}
// Destroy reg_ignore if some window above us invalidated it
if (!reg_ignore_valid) {
rc_region_unref(&w->reg_ignore);
}
// log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
log_trace("Checking whether window %#010x (%s) should be painted",
win_id(w), w->name);
// Give up if it's not damaged or invisible, or it's unmapped and its
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if ((w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYED) &&
w->running_animation_instance == NULL) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
(win_id(w) == ps->debug_window ||
win_client_id(w, /*fallback_to_self=*/false) == ps->debug_window)) {
log_trace("|- is the debug window");
to_paint = false;
} else if (!w->ever_damaged) {
log_trace("|- has not received any damages");
to_paint = false;
} else if (unlikely(w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 ||
w->g.x >= ps->root_width || w->g.y >= ps->root_height)) {
log_trace("|- is positioned outside of the screen");
to_paint = false;
} else if (unlikely(window_opacity * MAX_ALPHA < 1 &&
(!window_options.blur_background ||
blur_opacity * MAX_ALPHA < 1))) {
// For consistency, even a window has 0 opacity, we would still
// blur its background. (unless it's background is not blurred, or
// the blur opacity is 0)
log_trace("|- has 0 opacity");
to_paint = false;
} else if (!window_options.paint) {
log_trace("|- is excluded from painting");
to_paint = false;
} else if (unlikely((w->flags & WIN_FLAGS_PIXMAP_ERROR) != 0)) {
log_trace("|- has image errors");
to_paint = false;
}
// log_trace("%s %d %d %d", w->name, to_paint, w->opacity,
// w->paint_excluded);
// Add window to damaged area if its painting status changes
// or opacity changes
if (to_paint != was_painted) {
w->reg_ignore_valid = false;
add_damage_from_win(ps, w);
}
// to_paint will never change after this point
if (!to_paint) {
log_trace("|- will not be painted");
goto skip_window;
}
log_trace("|- will be painted");
log_verbose("Window %#010x (%s) will be painted", win_id(w), w->name);
// Generate ignore region for painting to reduce GPU load
if (!w->reg_ignore) {
w->reg_ignore = rc_region_ref(last_reg_ignore);
}
// If the window is solid, or we enabled clipping for transparent windows,
// we add the window region to the ignored region
// Otherwise last_reg_ignore shouldn't change
if ((w->mode != WMODE_TRANS && !ps->o.force_win_blend) ||
window_options.transparent_clipping) {
// w->mode == WMODE_SOLID or WMODE_FRAME_TRANS
region_t *tmp = rc_region_new();
if (w->mode == WMODE_SOLID) {
*tmp =
win_get_bounding_shape_global_without_corners_by_val(w);
} else {
// w->mode == WMODE_FRAME_TRANS
win_get_region_noframe_local_without_corners(w, tmp);
pixman_region32_intersect(tmp, tmp, &w->bounding_shape);
pixman_region32_translate(tmp, w->g.x, w->g.y);
}
pixman_region32_union(tmp, tmp, last_reg_ignore);
rc_region_unref(&last_reg_ignore);
last_reg_ignore = tmp;
}
// (Un)redirect screen
// We could definitely unredirect the screen when there's no window to
// paint, but this is typically unnecessary, may cause flickering when
// fading is enabled, and could create inconsistency when the wallpaper
// is not correctly set.
if (ps->o.unredir_if_possible && is_highest && w->mode == WMODE_SOLID &&
!ps->o.force_win_blend && w->is_fullscreen &&
(window_options.unredir == WINDOW_UNREDIR_WHEN_POSSIBLE ||
window_options.unredir == WINDOW_UNREDIR_WHEN_POSSIBLE_ELSE_TERMINATE)) {
unredir_possible = true;
}
// Unredirect screen if some window is forcing unredir, even when they are
// not on the top.
if (ps->o.unredir_if_possible &&
window_options.unredir == WINDOW_UNREDIR_FORCED) {
unredir_possible = true;
}
w->prev_trans = bottom;
bottom = w;
// If the screen is not redirected check if the window's unredir setting
// allows unredirection to be terminated.
if (ps->redirected || window_options.unredir == WINDOW_UNREDIR_TERMINATE ||
window_options.unredir == WINDOW_UNREDIR_WHEN_POSSIBLE_ELSE_TERMINATE) {
// Setting is_highest to false will stop all windows stacked below
// from triggering unredirection. But if `unredir_possible` is
// already set, this will not prevent unredirection.
is_highest = false;
}
skip_window:
reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid;
w->reg_ignore_valid = true;
if (w->state == WSTATE_DESTROYED && w->running_animation_instance == NULL) {
// the window should be destroyed because it was destroyed
// by X server and now its animations are finished
win_destroy_finish(ps, w);
w = NULL;
}
// Avoid setting w->to_paint if w is freed
if (w) {
w->to_paint = to_paint;
}
}
rc_region_unref(&last_reg_ignore);
// If possible, unredirect all windows and stop painting
if (ps->o.redirected_force != UNSET) {
unredir_possible = !ps->o.redirected_force;
} else if (ps->o.unredir_if_possible && is_highest && !ps->redirected) {
// `is_highest` being true means there's no window with a unredir setting
// that allows unredirection to be terminated. So if screen is not
// redirected, keep it that way.
//
// (might not be the best naming.)
unredir_possible = true;
}
if (unredir_possible) {
if (ps->redirected) {
if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit) {
unredirect(ps);
} else if (!ev_is_active(&ps->unredir_timer)) {
ev_timer_set(
&ps->unredir_timer,
(double)ps->o.unredir_if_possible_delay / 1000.0, 0);
ev_timer_start(ps->loop, &ps->unredir_timer);
}
}
} else {
ev_timer_stop(ps->loop, &ps->unredir_timer);
if (!ps->redirected) {
if (!redirect_start(ps)) {
return false;
}
}
}
*out_bottom = bottom;
return true;
}
void root_damaged(session_t *ps) {
if (ps->root_tile_paint.pixmap) {
free_root_tile(ps);
}
if (!ps->redirected) {
return;
}
if (ps->backend_data) {
if (ps->root_image) {
ps->backend_data->ops.release_image(ps->backend_data, ps->root_image);
ps->root_image = NULL;
}
auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
if (pixmap != XCB_NONE) {
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(
ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
if (!r) {