From 7d0d693ca7a4f07f94dbd66a1b22ddf29bd61c49 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 11 Nov 2022 15:51:11 +0000 Subject: [PATCH 001/177] backend: gl: fix shadow from mask The intermediate texture used for shadow from mask calculation did not properly set the min/mag filter to linear, which is required by the blur methods. Because they use texture interpolation to accelerate the convolution calculation. Fixes #916 Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 7ade23398d..88078e67ab 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1213,6 +1213,8 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, auto source_texture = gl_new_texture(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, source_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); From 9d7cbe49f16cf33db378c73627a080c78182d5eb Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 15 Nov 2022 18:03:31 +0000 Subject: [PATCH 002/177] win: assert we won't clobber existing mask Signed-off-by: Yuxuan Shui --- src/win.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win.c b/src/win.c index 370fbfd3d7..2525d89718 100644 --- a/src/win.c +++ b/src/win.c @@ -346,6 +346,7 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w } bool win_bind_mask(struct backend_base *b, struct managed_win *w) { + assert(!w->mask_image); auto reg_bound_local = win_get_bounding_shape_global_by_val(w); pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); w->mask_image = b->ops->make_mask( From 7233601be3f1ba291d5fe2be3347c6f1003a6d0e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 16 Nov 2022 15:39:17 +0000 Subject: [PATCH 003/177] Fix typo Fixes #922 Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 2 +- picom.sample.conf | 4 ++-- src/common.h | 4 ++-- src/options.c | 2 +- tests/configs/parsing_test.conf | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 7dc070f1d4..61af5a2c35 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -230,7 +230,7 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box GLX backend: Avoid rebinding pixmap on window damage. Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.). Recommended if it works. *--no-use-damage*:: - Disable the use of damage information. This cause the whole screen to be redrawn everytime, instead of the part of the screen has actually changed. Potentially degrades the performance, but might fix some artifacts. + Disable the use of damage information. This cause the whole screen to be redrawn every time, instead of the part of the screen has actually changed. Potentially degrades the performance, but might fix some artifacts. *--xrender-sync-fence*:: Use X Sync fence to sync clients' draw calls, to make sure all draw calls are finished before picom starts drawing. Needed on nvidia-drivers with GLX backend for some users. diff --git a/picom.sample.conf b/picom.sample.conf index 071a9945df..a8ba5c7ede 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -308,7 +308,7 @@ detect-transient = true; # glx-no-rebind-pixmap = false # Disable the use of damage information. -# This cause the whole screen to be redrawn everytime, instead of the part of the screen +# This cause the whole screen to be redrawn every time, instead of the part of the screen # has actually changed. Potentially degrades the performance, but might fix some artifacts. # The opposing option is use-damage # @@ -409,7 +409,7 @@ log-level = "warn"; # transparent, and you want shadows in those areas. # # clip-shadow-above::: -# Controls wether shadows that would have been drawn above the window should +# Controls whether shadows that would have been drawn above the window should # be clipped. Useful for dock windows that should have no shadow painted on top. # # redir-ignore::: diff --git a/src/common.h b/src/common.h index c06a30c242..c77318765b 100644 --- a/src/common.h +++ b/src/common.h @@ -150,7 +150,7 @@ typedef struct session { /// Use an ev_idle callback for drawing /// So we only start drawing when events are processed ev_idle draw_idle; - /// Called everytime we have timeouts or new data on socket, + /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue ev_prepare event_check; @@ -240,7 +240,7 @@ typedef struct session { /// Whether we need to redraw the screen bool redraw_needed; - /// Cache a xfixes region so we don't need to allocate it everytime. + /// Cache a xfixes region so we don't need to allocate it every time. /// A workaround for yshui/picom#301 xcb_xfixes_region_t damaged_region; /// The region needs to painted on next paint. diff --git a/src/options.c b/src/options.c index ba7485dd59..0226714953 100644 --- a/src/options.c +++ b/src/options.c @@ -139,7 +139,7 @@ static const struct picom_option picom_options[] = { {"log-file" , required_argument, 322, NULL , "Path to the log file."}, {"use-damage" , no_argument , 323, NULL , "Render only the damaged (changed) part of the screen"}, {"no-use-damage" , no_argument , 324, NULL , "Disable the use of damage information. This cause the whole screen to be" - "redrawn everytime, instead of the part of the screen that has actually " + "redrawn every time, instead of the part of the screen that has actually " "changed. Potentially degrades the performance, but might fix some artifacts."}, {"no-vsync" , no_argument , 325, NULL , "Disable VSync"}, {"max-brightness" , required_argument, 326, NULL , "Dims windows which average brightness is above this threshold. Requires " diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index 5269e6e842..72360173a8 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -311,7 +311,7 @@ detect-transient = true; # glx-no-rebind-pixmap = false # Disable the use of damage information. -# This cause the whole screen to be redrawn everytime, instead of the part of the screen +# This cause the whole screen to be redrawn every time, instead of the part of the screen # has actually changed. Potentially degrades the performance, but might fix some artifacts. # The opposing option is use-damage # @@ -400,7 +400,7 @@ log-level = "warn"; # transparent, and you want shadows in those areas. # # clip-shadow-above::: -# Controls wether shadows that would have been drawn above the window should +# Controls whether shadows that would have been drawn above the window should # be clipped. Useful for dock windows that should have no shadow painted on top. # # redir-ignore::: From e61b3ea7a303f090c00b6bc88a6945ff76f89255 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 17 Nov 2022 22:07:59 +0000 Subject: [PATCH 004/177] backend: gl: fix crash when shadow radius is 0 Fixes #927 Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 71 ++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 88078e67ab..f4f277d265 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1174,18 +1174,23 @@ struct gl_shadow_context { struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) { auto ctx = ccalloc(1, struct gl_shadow_context); ctx->radius = radius; - - struct gaussian_blur_args args = { - .size = (int)radius, - .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), - }; - ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + ctx->blur_context = NULL; + + if (radius > 0) { + struct gaussian_blur_args args = { + .size = (int)radius, + .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), + }; + ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + } return (struct backend_shadow_context *)ctx; } void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) { auto ctx_ = (struct gl_shadow_context *)ctx; - gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + if (ctx_->blur_context) { + gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + } free(ctx_); } @@ -1246,27 +1251,32 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, gl_check_err(); - glActiveTexture(GL_TEXTURE0); - auto tmp_texture = gl_new_texture(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, tmp_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, - GL_RED, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - tmp_texture, 0); - - region_t reg_blur; - pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, - (unsigned int)new_inner->height); - // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), but we - // are covering the whole texture so we don't need to worry about that. - gl_blur_impl(1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, - source_texture, - (geometry_t){.width = new_inner->width, .height = new_inner->height}, - fbo, gd->default_mask_texture); - pixman_region32_fini(®_blur); + auto tmp_texture = source_texture; + if (gsctx->blur_context != NULL) { + glActiveTexture(GL_TEXTURE0); + tmp_texture = gl_new_texture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, + new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tmp_texture, 0); + + region_t reg_blur; + pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, + (unsigned int)new_inner->height); + // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), + // but we are covering the whole texture so we don't need to worry about + // that. + gl_blur_impl( + 1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, + source_texture, + (geometry_t){.width = new_inner->width, .height = new_inner->height}, + fbo, gd->default_mask_texture); + pixman_region32_fini(®_blur); + } // Colorize the shadow with color. log_debug("Colorize shadow"); @@ -1318,7 +1328,10 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); - glDeleteTextures(1, (GLuint[]){source_texture, tmp_texture}); + glDeleteTextures(1, (GLuint[]){source_texture}); + if (tmp_texture != source_texture) { + glDeleteTextures(1, (GLuint[]){tmp_texture}); + } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); gl_check_err(); From 706acb78b7c5a1236fa727292e83baf3d7803d1b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 17 Nov 2022 22:16:27 +0000 Subject: [PATCH 005/177] backend: gl: handle blur context creation failure Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index f4f277d265..a7d2aabce2 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1182,6 +1182,11 @@ struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), }; ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); + if (!ctx->blur_context) { + log_error("Failed to create shadow context"); + free(ctx); + return NULL; + } } return (struct backend_shadow_context *)ctx; } From 5580f461dcf3465ce48dfce335b7c4661d67207b Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Fri, 18 Nov 2022 10:13:49 +0000 Subject: [PATCH 006/177] fix log_debug call --- src/picom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/picom.c b/src/picom.c index fd693fc927..ecd09078e8 100644 --- a/src/picom.c +++ b/src/picom.c @@ -539,8 +539,8 @@ static bool initialize_backend(session_t *ps) { } else { shader->attributes = 0; } - log_debug("Shader %s has attributes %ld", shader->key, - shader->attributes); + log_debug("Shader %s has attributes %" PRIu64, + shader->key, shader->attributes); } } From a9db7ab41eeaa8e1040550d23981a6edb0f71b61 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 21 Nov 2022 15:18:55 +0000 Subject: [PATCH 007/177] win: fix leaking of the mask image destroy_win_finish doesn't call win_release_images to free the images, so we need to add a release_mask call there. Related: #892 Signed-off-by: Yuxuan Shui --- src/win.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win.c b/src/win.c index 2525d89718..fb61191638 100644 --- a/src/win.c +++ b/src/win.c @@ -2140,6 +2140,7 @@ static void destroy_win_finish(session_t *ps, struct win *w) { assert(mw->shadow_image != NULL); win_release_shadow(ps->backend_data, mw); } + win_release_mask(ps->backend_data, mw); // Invalidate reg_ignore of windows below this one // TODO(yshui) what if next_w is not mapped?? From 2d35f78dc369906464dcb9f4470aed999f0e848e Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Fri, 18 Nov 2022 14:54:18 +0000 Subject: [PATCH 008/177] backend: egl: don't assume glEGLImageTargetTexStorage exists Use eglGetProcAddress instead. Fixes #932 --- src/backend/gl/egl.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 0c3c0d4840..5c3b553f9f 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -36,6 +36,8 @@ struct egl_data { EGLContext ctx; }; +static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL; + /** * Free a glx_texture_t. */ @@ -202,6 +204,14 @@ static backend_t *egl_init(session_t *ps) { goto end; } + glEGLImageTargetTexStorage = + (PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS" + "torageEXT"); + if (glEGLImageTargetTexStorage == NULL) { + log_error("Failed to get glEGLImageTargetTexStorageEXT."); + goto end; + } + gd->gl.decouple_texture_user_data = egl_decouple_user_data; gd->gl.release_user_data = egl_release_image; @@ -270,7 +280,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b wd->dim = 0; wd->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, inner->texture); - glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL); + glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); From e2b8c3fd1e5eb06c137daf03dcb6c11f93aa2cd4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 22 Nov 2022 16:12:36 +0000 Subject: [PATCH 009/177] c2: replace pcre with pcre2 Because pcre has been deprecated.[1] There are subtle changes from pcre to pcre2, so this could be a breaking change. Closes #895 [1]: https://www.pcre.org/original/changelog.txt Signed-off-by: Yuxuan Shui --- src/c2.c | 155 ++++++++++++++++++++++++------------------------ src/meson.build | 5 +- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/src/c2.c b/src/c2.c index 80ecb24cd1..f8af6992ed 100644 --- a/src/c2.c +++ b/src/c2.c @@ -17,15 +17,8 @@ // libpcre #ifdef CONFIG_REGEX_PCRE -#include - -// For compatibility with #endif @@ -89,49 +82,52 @@ struct _c2_b { /// Structure for leaf element in a window condition struct _c2_l { bool neg : 1; - enum { C2_L_OEXISTS, - C2_L_OEQ, - C2_L_OGT, - C2_L_OGTEQ, - C2_L_OLT, - C2_L_OLTEQ, + enum { + C2_L_OEXISTS, + C2_L_OEQ, + C2_L_OGT, + C2_L_OGTEQ, + C2_L_OLT, + C2_L_OLTEQ, } op : 3; - enum { C2_L_MEXACT, - C2_L_MSTART, - C2_L_MCONTAINS, - C2_L_MWILDCARD, - C2_L_MPCRE, + enum { + C2_L_MEXACT, + C2_L_MSTART, + C2_L_MCONTAINS, + C2_L_MWILDCARD, + C2_L_MPCRE, } match : 3; bool match_ignorecase : 1; char *tgt; xcb_atom_t tgtatom; bool tgt_onframe; int index; - enum { C2_L_PUNDEFINED = -1, - C2_L_PID = 0, - C2_L_PX, - C2_L_PY, - C2_L_PX2, - C2_L_PY2, - C2_L_PWIDTH, - C2_L_PHEIGHT, - C2_L_PWIDTHB, - C2_L_PHEIGHTB, - C2_L_PBDW, - C2_L_PFULLSCREEN, - C2_L_POVREDIR, - C2_L_PARGB, - C2_L_PFOCUSED, - C2_L_PWMWIN, - C2_L_PBSHAPED, - C2_L_PROUNDED, - C2_L_PCLIENT, - C2_L_PWINDOWTYPE, - C2_L_PLEADER, - C2_L_PNAME, - C2_L_PCLASSG, - C2_L_PCLASSI, - C2_L_PROLE, + enum { + C2_L_PUNDEFINED = -1, + C2_L_PID = 0, + C2_L_PX, + C2_L_PY, + C2_L_PX2, + C2_L_PY2, + C2_L_PWIDTH, + C2_L_PHEIGHT, + C2_L_PWIDTHB, + C2_L_PHEIGHTB, + C2_L_PBDW, + C2_L_PFULLSCREEN, + C2_L_POVREDIR, + C2_L_PARGB, + C2_L_PFOCUSED, + C2_L_PWMWIN, + C2_L_PBSHAPED, + C2_L_PROUNDED, + C2_L_PCLIENT, + C2_L_PWINDOWTYPE, + C2_L_PLEADER, + C2_L_PNAME, + C2_L_PCLASSG, + C2_L_PCLASSI, + C2_L_PROLE, } predef; enum c2_l_type { C2_L_TUNDEFINED, @@ -142,15 +138,16 @@ struct _c2_l { C2_L_TDRAWABLE, } type; int format; - enum { C2_L_PTUNDEFINED, - C2_L_PTSTRING, - C2_L_PTINT, + enum { + C2_L_PTUNDEFINED, + C2_L_PTSTRING, + C2_L_PTINT, } ptntype; char *ptnstr; long ptnint; #ifdef CONFIG_REGEX_PCRE - pcre *regex_pcre; - pcre_extra *regex_pcre_extra; + pcre2_code *regex_pcre; + pcre2_match_data *regex_pcre_match; #endif }; @@ -1059,32 +1056,31 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { // PCRE patterns if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) { #ifdef CONFIG_REGEX_PCRE - const char *error = NULL; - int erroffset = 0; - int options = 0; + int errorcode = 0; + PCRE2_SIZE erroffset = 0; + unsigned int options = 0; // Ignore case flag - if (pleaf->match_ignorecase) - options |= PCRE_CASELESS; + if (pleaf->match_ignorecase) { + options |= PCRE2_CASELESS; + } // Compile PCRE expression pleaf->regex_pcre = - pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL); - if (!pleaf->regex_pcre) { - log_error("Pattern \"%s\": PCRE regular expression parsing " + pcre2_compile((PCRE2_SPTR)pleaf->ptnstr, PCRE2_ZERO_TERMINATED, + options, &errorcode, &erroffset, NULL); + if (pleaf->regex_pcre == NULL) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errorcode, buffer, sizeof(buffer)); + log_error("Pattern \"%s\": PCRE regular expression " + "parsing " "failed on " - "offset %d: %s", - pleaf->ptnstr, erroffset, error); + "offset %zu: %s", + pleaf->ptnstr, erroffset, buffer); return false; } -#ifdef CONFIG_REGEX_PCRE_JIT - pleaf->regex_pcre_extra = - pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error); - if (!pleaf->regex_pcre_extra) { - printf("Pattern \"%s\": PCRE regular expression study failed: %s", - pleaf->ptnstr, error); - } -#endif + pleaf->regex_pcre_match = + pcre2_match_data_create_from_pattern(pleaf->regex_pcre, NULL); // Free the target string // free(pleaf->tgt); @@ -1102,16 +1098,18 @@ static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) { if (!node.isbranch) { return c2_l_postprocess(ps, node.l); } - if (!c2_tree_postprocess(ps, node.b->opr1)) + if (!c2_tree_postprocess(ps, node.b->opr1)) { return false; + } return c2_tree_postprocess(ps, node.b->opr2); } bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) { c2_lptr_t *head = list; while (head) { - if (!c2_tree_postprocess(ps, head->ptr)) + if (!c2_tree_postprocess(ps, head->ptr)) { return false; + } head = head->next; } return true; @@ -1124,8 +1122,9 @@ static void c2_free(c2_ptr_t p) { if (p.isbranch) { c2_b_t *const pbranch = p.b; - if (!pbranch) + if (!pbranch) { return; + } c2_free(pbranch->opr1); c2_free(pbranch->opr2); @@ -1135,14 +1134,15 @@ static void c2_free(c2_ptr_t p) { else { c2_l_t *const pleaf = p.l; - if (!pleaf) + if (!pleaf) { return; + } free(pleaf->tgt); free(pleaf->ptnstr); #ifdef CONFIG_REGEX_PCRE - pcre_free(pleaf->regex_pcre); - LPCRE_FREE_STUDY(pleaf->regex_pcre_extra); + pcre2_code_free(pleaf->regex_pcre); + pcre2_match_data_free(pleaf->regex_pcre_match); #endif free(pleaf); } @@ -1550,9 +1550,10 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w case C2_L_MPCRE: #ifdef CONFIG_REGEX_PCRE assert(strlen(tgt) <= INT_MAX); - res = (pcre_exec(pleaf->regex_pcre, - pleaf->regex_pcre_extra, tgt, - (int)strlen(tgt), 0, 0, NULL, 0) >= 0); + assert(pleaf->regex_pcre); + res = (pcre2_match(pleaf->regex_pcre, (PCRE2_SPTR)tgt, + strlen(tgt), 0, 0, + pleaf->regex_pcre_match, NULL) > 0); #else assert(0); #endif diff --git a/src/meson.build b/src/meson.build index 60d83a891f..09eb07b6c0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,11 +44,8 @@ if get_option('config_file') srcs += [ 'config_libconfig.c' ] endif if get_option('regex') - pcre = dependency('libpcre', required: true) + pcre = dependency('libpcre2-8', required: true) cflags += ['-DCONFIG_REGEX_PCRE'] - if pcre.version().version_compare('>=8.20') - cflags += ['-DCONFIG_REGEX_PCRE_JIT'] - endif deps += [pcre] endif From 7360522e315d7bfa85666f4e73a629d0ec060754 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Thu, 24 Nov 2022 00:08:37 +0100 Subject: [PATCH 010/177] picom.sample.conf: Add egl to backend option doc --- picom.sample.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/picom.sample.conf b/picom.sample.conf index a8ba5c7ede..7c74eb8227 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -209,7 +209,7 @@ blur-background-exclude = [ # Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. # daemon = false -# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. +# Specify the backend to use: `xrender`, `glx`, `egl` or `xr_glx_hybrid`. # `xrender` is the default one. # # backend = "glx" From 552bf77d0ebb4f61204eb4c7c03ea27cf1c03f46 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 27 Nov 2022 17:37:26 +0000 Subject: [PATCH 011/177] backend: egl: fix undefined symbols on old systems Users with an old EGL version won't be able to use the egl backend. OTOH we shouldn't prevent them from running picom because of a feature they won't even use. Don't assume the existence of EGL 1.5 symbols. Fixes #945 Signed-off-by: Yuxuan Shui --- src/backend/gl/egl.c | 46 ++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 5c3b553f9f..5c40b00dbb 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -37,6 +37,10 @@ struct egl_data { }; static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL; +static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL; +static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL; +static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL; +static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL; /** * Free a glx_texture_t. @@ -46,7 +50,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) { struct egl_pixmap *p = tex->user_data; // Release binding if (p->image != EGL_NO_IMAGE) { - eglDestroyImage(gd->display, p->image); + eglDestroyImageProc(gd->display, p->image); p->image = EGL_NO_IMAGE; } @@ -103,6 +107,20 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { * Initialize OpenGL. */ static backend_t *egl_init(session_t *ps) { + bool success = false; + +#define get_proc(name, type) \ + name##Proc = (type)eglGetProcAddress(#name); \ + if (!name##Proc) { \ + log_error("Failed to get EGL function " #name); \ + goto end; \ + } + get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC); + get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC); + get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC); + get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC); +#undef get_proc + // Check if we have the X11 platform const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (strstr(exts, "EGL_EXT_platform_x11") == NULL) { @@ -110,14 +128,13 @@ static backend_t *egl_init(session_t *ps) { return NULL; } - bool success = false; auto gd = ccalloc(1, struct egl_data); - gd->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_EXT, ps->dpy, - (EGLAttrib[]){ - EGL_PLATFORM_X11_SCREEN_EXT, - ps->scr, - EGL_NONE, - }); + gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, + (EGLAttrib[]){ + EGL_PLATFORM_X11_SCREEN_EXT, + ps->scr, + EGL_NONE, + }); if (gd->display == EGL_NO_DISPLAY) { log_error("Failed to get EGL display."); goto end; @@ -129,6 +146,11 @@ static backend_t *egl_init(session_t *ps) { goto end; } + if (major < 1 || (major == 1 && minor < 5)) { + log_error("EGL version too old, need at least 1.5."); + goto end; + } + // Check if EGL supports OpenGL const char *apis = eglQueryString(gd->display, EGL_CLIENT_APIS); if (strstr(apis, "OpenGL") == NULL) { @@ -172,7 +194,7 @@ static backend_t *egl_init(session_t *ps) { EGLConfig target_cfg = cfgs[0]; free(cfgs); - gd->target_win = eglCreatePlatformWindowSurface( + gd->target_win = eglCreatePlatformWindowSurfaceProc( gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); @@ -260,8 +282,8 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b eglpixmap = cmalloc(struct egl_pixmap); eglpixmap->pixmap = pixmap; - eglpixmap->image = eglCreateImage(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, - (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->image = eglCreateImageProc(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); eglpixmap->owned = owned; if (eglpixmap->image == EGL_NO_IMAGE) { @@ -287,7 +309,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b return wd; err: if (eglpixmap && eglpixmap->image) { - eglDestroyImage(gd->display, eglpixmap->image); + eglDestroyImageProc(gd->display, eglpixmap->image); } free(eglpixmap); From 19a24ada9dcd4ff39d98fed900c32bb3533be697 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 27 Nov 2022 18:07:05 +0000 Subject: [PATCH 012/177] backend: egl: fix warning --- src/backend/gl/egl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 5c40b00dbb..e6d4d9018a 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -108,6 +108,7 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { */ static backend_t *egl_init(session_t *ps) { bool success = false; + struct egl_data *gd = NULL; #define get_proc(name, type) \ name##Proc = (type)eglGetProcAddress(#name); \ @@ -128,7 +129,7 @@ static backend_t *egl_init(session_t *ps) { return NULL; } - auto gd = ccalloc(1, struct egl_data); + gd = ccalloc(1, struct egl_data); gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, (EGLAttrib[]){ EGL_PLATFORM_X11_SCREEN_EXT, @@ -249,7 +250,9 @@ static backend_t *egl_init(session_t *ps) { end: if (!success) { - egl_deinit(&gd->gl.base); + if (gd != NULL) { + egl_deinit(&gd->gl.base); + } return NULL; } From 0a2cd0f14eebc41cc4c7b7c9a3db964a2b1a5ab0 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 04:16:15 +0000 Subject: [PATCH 013/177] backend: gl: add dither Add bayer ordered dithering when presenting to screen. Reduce banding when using a strong blur. Also use 16-bit intermediary textures to preserve precision in the rendering pipeline. Related: #602 Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 2 +- src/backend/gl/gl_common.c | 6 ++++-- src/backend/gl/gl_common.h | 5 +++-- src/backend/gl/shaders.c | 30 ++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index b73aeeeb8e..aad1e5fe52 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -284,7 +284,7 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, } glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, tex_size->width, tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index a7d2aabce2..cfa98d1a25 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -627,7 +627,7 @@ void gl_resize(struct gl_data *gd, int width, int height) { assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); gl_check_err(); @@ -873,7 +873,9 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); + gd->present_prog = + gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL}, + (const char *[]){present_frag, dither_glsl, NULL}); if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 3a7886518b..bad75ed618 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -288,5 +288,6 @@ static const GLuint vert_in_texcoord_loc = 1; #define QUOTE(...) #__VA_ARGS__ extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], - fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[], - win_shader_default[], present_vertex_shader[], shadow_colorization_frag[]; + present_frag[], fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], + win_shader_glsl[], win_shader_default[], present_vertex_shader[], dither_glsl[], + shadow_colorization_frag[]; diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 4a18e62874..bd620fe7f9 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -9,6 +9,15 @@ const char dummy_frag[] = GLSL(330, } ); +const char present_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + vec4 dither(vec4, vec2); + void main() { + gl_FragColor = dither(texelFetch(tex, ivec2(texcoord.xy), 0), texcoord); + } +); + const char copy_with_mask_frag[] = GLSL(330, uniform sampler2D tex; in vec2 texcoord; @@ -174,6 +183,27 @@ const char vertex_shader[] = GLSL(330, texcoord = in_texcoord + texorig; } ); +const char dither_glsl[] = GLSL(330, + // Stolen from: https://www.shadertoy.com/view/7sfXDn + float bayer2(vec2 a) { + a = floor(a); + return fract(a.x / 2. + a.y * a.y * .75); + } + // 16 * 16 is 2^8, so in total we have equivalent of 16-bit + // color depth, should be enough? + float bayer(vec2 a16) { + vec2 a8 = a16 * .5; + vec2 a4 = a8 * .5; + vec2 a2 = a4 * .5; + float bayer32 = ((bayer2(a2) * .25 + bayer2( a4)) + * .25 + bayer2( a8)) + * .25 + bayer2(a16); + return bayer32; + } + vec4 dither(vec4 c, vec2 coord) { + return vec4(c + bayer(coord) / 255.0); + } +); const char shadow_colorization_frag[] = GLSL(330, uniform vec4 color; uniform sampler2D tex; From 1271839bafbbbea3545cbcf1b936d1ac41135531 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 05:28:57 +0000 Subject: [PATCH 014/177] options: add dithered-present option See also 0a2cd0f14eebc41cc4c7b7c9a3db964a2b1a5ab0 Related: #602 Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 3 +++ picom.sample.conf | 5 +++++ src/backend/gl/blur.c | 16 ++++++++++------ src/backend/gl/gl_common.c | 22 ++++++++++++++++------ src/backend/gl/gl_common.h | 10 ++++++---- src/backend/xrender/xrender.c | 4 ++++ src/config.h | 2 ++ src/config_libconfig.c | 2 ++ src/options.c | 8 ++++++++ 9 files changed, 56 insertions(+), 16 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 61af5a2c35..5b2182f6e4 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -268,6 +268,9 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--window-shader-fg-rule* 'SHADER':'CONDITION':: Specify GLSL fragment shader path for rendering window contents using patterns. Similar to *--opacity-rule*, arguments should be in the format of 'SHADER:CONDITION', e.g. "shader.frag:name = \'window\'". Leading and trailing whitespaces in 'SHADER' will be trimmed. If 'SHADER' is "default", then the default shader will be used for the matching windows. (This also unfortunately means you can't use a shader file named "default"). Does not work when *--legacy-backends* is enabled. +*--dithered-present* + Use higher precision during rendering, and apply dither when presenting the rendered screen. Reduces banding artifacts, but might cause performance degradation. Only works with OpenGL. + FORMAT OF CONDITIONS -------------------- Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators. diff --git a/picom.sample.conf b/picom.sample.conf index 7c74eb8227..f0c7a795d5 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -215,6 +215,11 @@ blur-background-exclude = [ # backend = "glx" backend = "xrender"; +# Use higher precision during rendering, and apply dither when presenting the +# rendered screen. Reduces banding artifacts, but might cause performance +# degradation. Only works with OpenGL. +dithered-present = false; + # Enable/disable VSync. # vsync = false vsync = true; diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index aad1e5fe52..929703816b 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -259,10 +259,10 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec return true; } -bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, - coord_t mask_dst, const region_t *reg_blur, - const region_t *reg_visible attr_unused, GLuint source_texture, - geometry_t source_size, GLuint target_fbo, GLuint default_mask) { +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused, + GLuint source_texture, geometry_t source_size, GLuint target_fbo, + GLuint default_mask, bool high_precision) { bool ret = false; if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) { @@ -284,7 +284,11 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, } glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, tex_size->width, + GLint format = GL_RGBA8; + if (high_precision) { + format = GL_RGBA16; + } + glTexImage2D(GL_TEXTURE_2D, 0, format, tex_size->width, tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { @@ -406,7 +410,7 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mas return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible, gd->back_texture, (geometry_t){.width = gd->width, .height = gd->height}, - gd->back_fbo, gd->default_mask_texture); + gd->back_fbo, gd->default_mask_texture, gd->dithered_present); } static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index cfa98d1a25..67b7e64030 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -622,13 +622,16 @@ void gl_resize(struct gl_data *gd, int width, int height) { gd->height = height; gd->width = width; + GLint format = GL_RGB8; + if (gd->dithered_present) { + format = GL_RGB16; + } assert(viewport_dimensions[0] >= gd->width); assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, width, height, 0, GL_BGR, - GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); gl_check_err(); } @@ -873,9 +876,16 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - gd->present_prog = - gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL}, - (const char *[]){present_frag, dither_glsl, NULL}); + gd->dithered_present = ps->o.dithered_present; + if (gd->dithered_present) { + gd->present_prog = gl_create_program_from_strv( + (const char *[]){present_vertex_shader, NULL}, + (const char *[]){present_frag, dither_glsl, NULL}); + } else { + gd->present_prog = gl_create_program_from_strv( + (const char *[]){present_vertex_shader, NULL}, + (const char *[]){dummy_frag, NULL}); + } if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; @@ -1281,7 +1291,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, 1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, source_texture, (geometry_t){.width = new_inner->width, .height = new_inner->height}, - fbo, gd->default_mask_texture); + fbo, gd->default_mask_texture, gd->dithered_present); pixman_region32_fini(®_blur); } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index bad75ed618..b7ef2b486f 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -109,6 +109,8 @@ struct gl_data { GLuint back_texture, back_fbo; GLuint present_prog; + bool dithered_present; + GLuint default_mask_texture; /// Called when an gl_texture is decoupled from the texture it refers. Returns @@ -163,10 +165,10 @@ void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visi bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible); -bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, - coord_t mask_dst, const region_t *reg_blur, - const region_t *reg_visible attr_unused, GLuint source_texture, - geometry_t source_size, GLuint target_fbo, GLuint default_mask); +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused, + GLuint source_texture, geometry_t source_size, GLuint target_fbo, + GLuint default_mask, bool high_precision); void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); void gl_destroy_blur_context(backend_t *base, void *ctx); struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius); diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 2b7f8e17af..e85a85fab7 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -858,6 +858,10 @@ static void get_blur_size(void *blur_context, int *width, int *height) { } static backend_t *backend_xrender_init(session_t *ps) { + if (ps->o.dithered_present) { + log_warn("\"dithered-present\" is not supported by the xrender backend."); + } + auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); diff --git a/src/config.h b/src/config.h index 7259dc1d35..3f11e1e7e1 100644 --- a/src/config.h +++ b/src/config.h @@ -256,6 +256,8 @@ typedef struct options { /// A list of conditions of windows to which transparent clipping /// should not apply c2_lptr_t *transparent_clipping_blacklist; + + bool dithered_present; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 461fff3be9..ba987a8908 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -452,6 +452,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen); // --transparent-clipping lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping); + // --dithered_present + lcfg_lookup_bool(&cfg, "dithered-present", &opt->dithered_present); // --transparent-clipping-exclude parse_cfg_condlst(&cfg, &opt->transparent_clipping_blacklist, "transparent-clipping-exclude"); diff --git a/src/options.c b/src/options.c index 0226714953..d6138b9413 100644 --- a/src/options.c +++ b/src/options.c @@ -168,6 +168,10 @@ static const struct picom_option picom_options[] = { "similar to --opacity-rule. SHADER_PATH can be \"default\", in which case " "the default shader will be used. Does not work when --legacy-backends is " "enabled. See man page for more details"}, + // 338 is transparent-clipping-exclude + {"dithered-present" , no_argument , 339, NULL , "Use higher precision during rendering, and apply dither when presenting the " + "rendered screen. Reduces banding artifacts, but might cause performance " + "degradation. Only works with OpenGL."}, {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, @@ -716,6 +720,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --clip-shadow-above condlst_add(&opt->shadow_clip_list, optarg); break; + case 339: + // --dithered-present + opt->dithered_present = true; + break; P_CASEBOOL(733, legacy_backends); P_CASEBOOL(800, monitor_repaint); case 801: From 1a82b8180a596594797f880a836b78e18b95c773 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 29 Nov 2022 22:20:22 -0800 Subject: [PATCH 015/177] Change dreaw -> draw --- src/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index 0226714953..631f540dea 100644 --- a/src/options.c +++ b/src/options.c @@ -46,7 +46,7 @@ static const struct picom_option picom_options[] = { {"fade-delta" , required_argument, 'D', NULL , "The time between steps in a fade in milliseconds. (default 10)"}, {"menu-opacity" , required_argument, 'm', NULL , "The opacity for menus. (default 1.0)"}, {"shadow" , no_argument , 'c', NULL , "Enabled client-side shadows on windows."}, - {"clear-shadow" , no_argument , 'z', NULL , "Don't dreaw shadow behind the window."}, + {"clear-shadow" , no_argument , 'z', NULL , "Don't draw shadow behind the window."}, {"fading" , no_argument , 'f', NULL , "Fade windows in/out when opening/closing and when opacity changes, " "unless --no-fading-openclose is used."}, {"inactive-opacity" , required_argument, 'i', NULL , "Opacity of inactive windows. (0.1 - 1.0)"}, From d704e0f80e1df1198cc229b8abbc88eb3caec862 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 12:13:09 +0000 Subject: [PATCH 016/177] backend: gl: don't add dither where it's not needed If a pixel is perfectly representable as an 8-bit number, don't add dither. Reduce artifacts where dither is unnecessary. Signed-off-by: Yuxuan Shui --- src/backend/gl/shaders.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index bd620fe7f9..90f636b7e7 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -201,7 +201,9 @@ const char dither_glsl[] = GLSL(330, return bayer32; } vec4 dither(vec4 c, vec2 coord) { - return vec4(c + bayer(coord) / 255.0); + vec4 residual = mod(c, 1.0 / 255.0); + vec4 dithered = vec4(greaterThan(residual, vec4(1e-4))); + return vec4(c + dithered * bayer(coord) / 255.0); } ); const char shadow_colorization_frag[] = GLSL(330, From de3e1a80eb93f11cc0ad27cb46db35fcb6a4a5ac Mon Sep 17 00:00:00 2001 From: Evgeniy Baskov Date: Wed, 30 Nov 2022 20:00:46 +0300 Subject: [PATCH 017/177] win: consider border when creating the mask image With rounded corners, X11 native border and blur enabled, left and bottom 2*border_width pixels were not blurred, since mask did not include border_width, only content width and height. Create mask image with dimensions that include border width. Signed-off-by: Evgeniy Baskov --- src/win.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win.c b/src/win.c index fb61191638..502bb82a63 100644 --- a/src/win.c +++ b/src/win.c @@ -350,7 +350,7 @@ bool win_bind_mask(struct backend_base *b, struct managed_win *w) { auto reg_bound_local = win_get_bounding_shape_global_by_val(w); pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); w->mask_image = b->ops->make_mask( - b, (geometry_t){.width = w->g.width, .height = w->g.height}, ®_bound_local); + b, (geometry_t){.width = w->widthb, .height = w->heightb}, ®_bound_local); pixman_region32_fini(®_bound_local); if (!w->mask_image) { From 8b4160123ed9b3a4780c682727322d7cc5de0ab4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 30 Nov 2022 19:23:47 +0000 Subject: [PATCH 018/177] README: update pcre requirements Signed-off-by: Yuxuan Shui --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ccf03383f4..49ffedf06c 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,20 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) * libGL, libEGL (optional, disable with the `-Dopengl=false` meson configure flag) -* libpcre (optional, disable with the `-Dregex=false` meson configure flag) +* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag) * libev * uthash On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libpcre3-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are ``` -dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel +dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel ``` To build the documents, you need `asciidoc` From 756757ee76269962dbbcddf5fcb746929571697c Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 1 Dec 2022 20:55:57 +0300 Subject: [PATCH 019/177] README: fix meson's warnings about setup commands this fixes `WARNING: Running the setup command as `meson [options]` instead of `meson setup [options]` is ambiguous and deprecated.` warning while setting up the project --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 49ffedf06c..b1711ebd45 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ To build the documents, you need `asciidoc` ```bash $ git submodule update --init --recursive -$ meson --buildtype=release . build +$ meson setup --buildtype=release . build $ ninja -C build ``` @@ -70,13 +70,12 @@ If you have libraries and/or headers installed at non-default location (e.g. und You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: ```bash -$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build - +$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release . build ``` As an example, on FreeBSD, you might have to run meson with: ```bash -$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build +$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release . build $ ninja -C build ``` From d38b0ead7d516d7db0782261ea8c46e7477f1331 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 1 Dec 2022 18:54:22 +0000 Subject: [PATCH 020/177] backend: gl: try different back buffer formats Prefer RGB formats first, because they use less memory; but fallback to RGBA formats, as they are formats required by OpenGL. Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 30 +++++++++++++++++++----------- src/backend/gl/gl_common.h | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 67b7e64030..44522b0c81 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -622,16 +622,13 @@ void gl_resize(struct gl_data *gd, int width, int height) { gd->height = height; gd->width = width; - GLint format = GL_RGB8; - if (gd->dithered_present) { - format = GL_RGB16; - } assert(viewport_dimensions[0] >= gd->width); assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, gd->back_format, width, height, 0, GL_BGR, + GL_UNSIGNED_BYTE, NULL); gl_check_err(); } @@ -919,14 +916,25 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - // Set up the size of the back texture - gl_resize(gd, ps->root_width, ps->root_height); - + // Set up the size and format of the back texture glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - gd->back_texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + const GLint *format = (const GLint[]){GL_RGB8, GL_RGBA8}; + if (gd->dithered_present) { + format = (const GLint[]){GL_RGB16, GL_RGBA16}; + } + for (int i = 0; i < 2; i++) { + gd->back_format = format[i]; + gl_resize(gd, ps->root_width, ps->root_height); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, gd->back_texture, 0); + if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { + log_info("Using back buffer format %#x", gd->back_format); + break; + } + } + if (!gl_check_fb_complete(GL_DRAW_FRAMEBUFFER)) { return false; } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index b7ef2b486f..4aad9c0d50 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -107,6 +107,7 @@ struct gl_data { gl_fill_shader_t fill_shader; gl_shadow_shader_t shadow_shader; GLuint back_texture, back_fbo; + GLint back_format; GLuint present_prog; bool dithered_present; From 8143c07de62371e3e1580addf8214b7d8cf61972 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 1 Dec 2022 21:21:19 +0000 Subject: [PATCH 021/177] Update README.md Signed-off-by: Yuxuan Shui --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b1711ebd45..f6bdfe1840 100644 --- a/README.md +++ b/README.md @@ -89,15 +89,21 @@ Default install prefix is `/usr/local`, you can change it with `meson configure ## How to Contribute -### Code +All contributions are welcome! -You can look at the [Projects](https://github.com/yshui/picom/projects) page, and see if there is anything that interests you. Or you can take a look at the [Issues](https://github.com/yshui/picom/issues). +New features you think should be included in picom, a fix for a bug you found - please open a PR! -### Non-code +You can take a look at the [Issues](https://github.com/yshui/picom/issues). -Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find. +Contributions to the documents and wiki are also appreciated. -Contributions to the documents and wiki will also be appreciated. +Even if you don't want to add anything to picom, you are still helping by compiling and running this branch, and report any issue you can find. + +### Become a Collaborator + +Becoming a collaborator of picom requires significant time commitment. You are expected to reply to issue reports, reviewing PRs, and sometimes fix bugs or implement new feature. You won't be able to push to the main branch directly, and all you code still has to go through code review. + +If this sounds good to you, feel free to contact me. ## Contributors From 236c8228b8459e40067822006797d6b5c45dc9ba Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 2 Dec 2022 02:57:28 +0000 Subject: [PATCH 022/177] backend: gl: fix use-after-scope 'format' was pointing to an array with a shorter lifetime suggested by @tryone144 Co-authored-by: Bernd Busse --- src/backend/gl/gl_common.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 44522b0c81..9789eb51cf 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -919,10 +919,8 @@ bool gl_init(struct gl_data *gd, session_t *ps) { // Set up the size and format of the back texture glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); glDrawBuffer(GL_COLOR_ATTACHMENT0); - const GLint *format = (const GLint[]){GL_RGB8, GL_RGBA8}; - if (gd->dithered_present) { - format = (const GLint[]){GL_RGB16, GL_RGBA16}; - } + const GLint *format = gd->dithered_present ? (const GLint[]){GL_RGB16, GL_RGBA16} + : (const GLint[]){GL_RGB8, GL_RGBA8}; for (int i = 0; i < 2; i++) { gd->back_format = format[i]; gl_resize(gd, ps->root_width, ps->root_height); From 0d2b14d0c3435c72ce8b3f75c627a0fa937809fa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 2 Dec 2022 17:51:30 +0000 Subject: [PATCH 023/177] man: fix typo Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 61af5a2c35..e8c4cec9d1 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -481,7 +481,7 @@ Available options of the 'blur' section are: :: An integer in the range 0-20. The strength of the 'dual_kawase' blur method. Corresponds to the *--blur-strength* command line option. If set to zero, the value requested by *--blur-size* is approximated (default: 5). *kernel*::: - A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kerns* option. Corresponds to the *--blur-kerns* command line option. + A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kern* option. Corresponds to the *--blur-kern* command line option. SIGNALS ------- From 91e023971d06b441c5b086ffc49e148d9b0c601f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 12 Dec 2022 08:51:42 +0000 Subject: [PATCH 024/177] utils: add rolling_max For tracking rolling max of a stream of integers. Signed-off-by: Yuxuan Shui --- src/utils.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.h | 8 ++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/utils.c b/src/utils.c index 8a27f393fd..8190226ce3 100644 --- a/src/utils.c +++ b/src/utils.c @@ -4,6 +4,7 @@ #include "compiler.h" #include "string_utils.h" +#include "test.h" #include "utils.h" /// Report allocation failure without allocating memory @@ -36,8 +37,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int /// Calculates next closest power of two of 32bit integer n /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 /// -int next_power_of_two(int n) -{ +int next_power_of_two(int n) { n--; n |= n >> 1; n |= n >> 2; @@ -48,4 +48,120 @@ int next_power_of_two(int n) return n; } +/// Track the rolling maximum of a stream of integers. +struct rolling_max { + /// A priority queue holding the indices of the maximum element candidates. + /// The head of the queue is the index of the maximum element. + /// The indices in the queue are the "original" indices. + /// + /// There are only `capacity` elements in `elem`, all previous elements are + /// discarded. But the discarded elements' indices are not forgotten, that's why + /// it's called the "original" indices. + int *p; + int p_head, np; + + /// The elemets + int *elem; + int elem_head, nelem; + + int window_size; +}; + +void rolling_max_destroy(struct rolling_max *rm) { + free(rm->elem); + free(rm->p); + free(rm); +} + +struct rolling_max *rolling_max_new(int size) { + auto rm = ccalloc(1, struct rolling_max); + if (!rm) { + return NULL; + } + + rm->p = ccalloc(size, int); + rm->elem = ccalloc(size, int); + rm->window_size = size; + if (!rm->p || !rm->elem) { + goto err; + } + + return rm; + +err: + rolling_max_destroy(rm); + return NULL; +} + +void rolling_max_reset(struct rolling_max *rm) { + rm->p_head = 0; + rm->np = 0; + rm->nelem = 0; + rm->elem_head = 0; +} + +void rolling_max_push(struct rolling_max *rm, int val) { +#define IDX(n) ((n) % rm->window_size) + if (rm->nelem == rm->window_size) { + auto old_head = rm->elem_head; + // Discard the oldest element. + // rm->elem.pop_front(); + rm->nelem--; + rm->elem_head = IDX(rm->elem_head + 1); + + // Remove discarded element from the priority queue too. + assert(rm->np); + if (rm->p[rm->p_head] == old_head) { + // rm->p.pop_front() + rm->p_head = IDX(rm->p_head + 1); + rm->np--; + } + } + + // Add the new element to the queue. + // rm->elem.push_back(val) + rm->elem[IDX(rm->elem_head + rm->nelem)] = val; + rm->nelem++; + + // Update the prority queue. + // Remove all elements smaller than the new element from the queue. Because + // the new element will become the maximum element before them, and since they + // come b1efore the new element, they will have been popped before the new + // element, so they will never become the maximum element. + while (rm->np) { + int p_tail = IDX(rm->p_head + rm->np - 1); + if (rm->elem[rm->p[p_tail]] > val) { + break; + } + // rm->p.pop_back() + rm->np--; + } + // Add the new element to the end of the queue. + // rm->p.push_back(rm->start_index + rm->nelem - 1) + rm->p[IDX(rm->p_head + rm->np)] = IDX(rm->elem_head + rm->nelem - 1); + rm->np++; +#undef IDX +} + +int rolling_max_get_max(struct rolling_max *rm) { + if (rm->np == 0) { + return INT_MIN; + } + return rm->elem[rm->p[rm->p_head]]; +} + +TEST_CASE(rolling_max_test) { +#define NELEM 15 + auto rm = rolling_max_new(3); + const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; + const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2}; + int max[NELEM] = {0}; + for (int i = 0; i < NELEM; i++) { + rolling_max_push(rm, data[i]); + max[i] = rolling_max_get_max(rm); + } + TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0); +#undef NELEM +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index a35bfa84b3..a1a3d34f9f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -289,6 +289,14 @@ static inline void free_charpp(char **str) { /// int next_power_of_two(int n); +struct rolling_max; + +struct rolling_max *rolling_max_new(int window_size); +void rolling_max_free(struct rolling_max *rm); +void rolling_max_reset(struct rolling_max *rm); +void rolling_max_push(struct rolling_max *rm, int val); +int rolling_max_get_max(struct rolling_max *rm); + // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. #ifdef __ANDROID__ From 459416894657b9791da1205f0da57362a5d61b1f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 13 Dec 2022 08:39:11 +0000 Subject: [PATCH 025/177] utils: add rolling_avg Signed-off-by: Yuxuan Shui --- src/utils.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 7 +++++ 2 files changed, 86 insertions(+) diff --git a/src/utils.c b/src/utils.c index 8190226ce3..c080e53eb8 100644 --- a/src/utils.c +++ b/src/utils.c @@ -164,4 +164,83 @@ TEST_CASE(rolling_max_test) { #undef NELEM } +/// A rolling average of a stream of integers. +struct rolling_avg { + /// The sum of the elements in the window. + int64_t sum; + + /// The elements in the window. + int *elem; + int head, nelem; + + int window_size; +}; + +struct rolling_avg *rolling_avg_new(int size) { + auto rm = ccalloc(1, struct rolling_avg); + if (!rm) { + return NULL; + } + + rm->elem = ccalloc(size, int); + rm->window_size = size; + if (!rm->elem) { + free(rm); + return NULL; + } + + return rm; +} + +void rolling_avg_destroy(struct rolling_avg *rm) { + free(rm->elem); + free(rm); +} + +void rolling_avg_reset(struct rolling_avg *ra) { + ra->sum = 0; + ra->nelem = 0; + ra->head = 0; +} + +void rolling_avg_push(struct rolling_avg *ra, int val) { + if (ra->nelem == ra->window_size) { + // Discard the oldest element. + // rm->elem.pop_front(); + ra->sum -= ra->elem[ra->head % ra->window_size]; + ra->nelem--; + ra->head = (ra->head + 1) % ra->window_size; + } + + // Add the new element to the queue. + // rm->elem.push_back(val) + ra->elem[(ra->head + ra->nelem) % ra->window_size] = val; + ra->sum += val; + ra->nelem++; +} + +double rolling_avg_get_avg(struct rolling_avg *ra) { + if (ra->nelem == 0) { + return 0; + } + return (double)ra->sum / (double)ra->nelem; +} + +TEST_CASE(rolling_avg_test) { +#define NELEM 15 + auto rm = rolling_avg_new(3); + const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; + const double expected_avg[NELEM] = { + 1, 1.5, 2, 2, 8.0 / 3.0, 10.0 / 3.0, 11.0 / 3.0, 10.0 / 3.0, + 11.0 / 3.0, 14.0 / 3.0, 5, 4, 3, 5.0 / 3.0, 2.0 / 3.0}; + double avg[NELEM] = {0}; + for (int i = 0; i < NELEM; i++) { + rolling_avg_push(rm, data[i]); + avg[i] = rolling_avg_get_avg(rm); + } + for (int i = 0; i < NELEM; i++) { + TEST_EQUAL(avg[i], expected_avg[i]); + } +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index a1a3d34f9f..5fa92199bb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -297,6 +297,13 @@ void rolling_max_reset(struct rolling_max *rm); void rolling_max_push(struct rolling_max *rm, int val); int rolling_max_get_max(struct rolling_max *rm); +struct rolling_avg; +struct rolling_avg *rolling_avg_new(int window_size); +void rolling_avg_free(struct rolling_avg *ra); +void rolling_avg_reset(struct rolling_avg *ra); +void rolling_avg_push(struct rolling_avg *ra, int val); +double rolling_avg_get_avg(struct rolling_avg *ra); + // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. #ifdef __ANDROID__ From 88608027b82d3777a6eed111018013640e1f3636 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:36:07 +0000 Subject: [PATCH 026/177] backend: add a comment Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/backend.c b/src/backend/backend.c index 032c301b3f..83af8fb63c 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -145,6 +145,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_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 + // + // TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint? int resize_factor = 1; if (t) { resize_factor = t->stacking_rank; From e407c5c7652da7e340e7c7797723b78be7ab1ab7 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:37:27 +0000 Subject: [PATCH 027/177] x: add x_{create,destroy}_region Signed-off-by: Yuxuan Shui --- src/x.c | 32 ++++++++++++++++++++++++++++++++ src/x.h | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/src/x.c b/src/x.c index 795211dbcc..7faa1c984a 100644 --- a/src/x.c +++ b/src/x.c @@ -388,6 +388,38 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_ return ret; } +uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { + if (!reg) { + return XCB_NONE; + } + + int nrects; + auto rects = pixman_region32_rectangles(reg, &nrects); + auto xrects = ccalloc(nrects, xcb_rectangle_t); + for (int i = 0; i < nrects; i++) { + xrects[i] = + (xcb_rectangle_t){.x = to_i16_checked(rects[i].x1), + .y = to_i16_checked(rects[i].y1), + .width = to_u16_checked(rects[i].x2 - rects[i].x1), + .height = to_u16_checked(rects[i].y2 - rects[i].y1)}; + } + + xcb_xfixes_region_t ret = x_new_id(c); + bool success = + XCB_AWAIT_VOID(xcb_xfixes_create_region, c, ret, to_u32_checked(nrects), xrects); + free(xrects); + if (!success) { + return XCB_NONE; + } + return ret; +} + +void x_destroy_region(xcb_connection_t *c, xcb_xfixes_region_t r) { + if (r != XCB_NONE) { + xcb_xfixes_destroy_region(c, r); + } +} + void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, int16_t clip_x_origin, int16_t clip_y_origin, const region_t *reg) { diff --git a/src/x.h b/src/x.h index 3b8787c7c5..a19288623e 100644 --- a/src/x.h +++ b/src/x.h @@ -216,6 +216,12 @@ x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, /// Fetch a X region and store it in a pixman region bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); +/// Create a X region from a pixman region +uint32_t x_create_region(xcb_connection_t *c, const region_t *reg); + +/// Destroy a X region +void x_destroy_region(xcb_connection_t *c, uint32_t region); + void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, int16_t clip_y_origin, const region_t *); From 23a29470e50ef1d68b9c99186b069659a793184c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 07:40:42 +0000 Subject: [PATCH 028/177] backend: xrender: set update region for PresentPixmap request Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index e85a85fab7..19c2ebff8d 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -606,13 +606,16 @@ static void present(backend_t *base, const region_t *region) { XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, 0, orig_x, orig_y, region_width, region_height); + auto xregion = x_create_region(base->c, region); + // Make sure we got reply from PresentPixmap before waiting for events, // to avoid deadlock auto e = xcb_request_check( base->c, xcb_present_pixmap_checked( xd->base.c, xd->target_win, - xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0, + xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL)); + x_destroy_region(base->c, xregion); if (e) { log_error("Failed to present pixmap"); free(e); From 8b189bc5ec1e1ed3aa601fc50eff1043307904a4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 12:35:37 +0000 Subject: [PATCH 029/177] core: print error with full_sequence ev->sequence was just the lower 16 bits of the sequence number. Signed-off-by: Yuxuan Shui --- src/picom.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/picom.c b/src/picom.c index ecd09078e8..0e40d45a33 100644 --- a/src/picom.c +++ b/src/picom.c @@ -297,7 +297,7 @@ void discard_ignore(session_t *ps, unsigned long sequence) { } } -static int should_ignore(session_t *ps, unsigned long sequence) { +static int should_ignore(session_t *ps, uint32_t sequence) { if (ps == NULL) { // Do not ignore errors until the session has been initialized return false; @@ -964,7 +964,7 @@ void root_damaged(session_t *ps) { * Xlib error handler function. */ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, ev->serial)) { + if (!should_ignore(ps_g, (uint32_t)ev->serial)) { x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); } return 0; @@ -974,8 +974,9 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { * XCB error handler function. */ void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->sequence)) { - x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); + if (!should_ignore(ps, err->full_sequence)) { + x_print_error(err->full_sequence, err->major_code, err->minor_code, + err->error_code); } } From 5a5ea76006125711061a72f4f972c58b48c2ab1d Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:35:11 +0000 Subject: [PATCH 030/177] event: restore event sequence number after passing it to Xlib handlers We set event sequence number to the last sequence xlib knows about to silence its complaint about missing sequence numbers, but we forgot to restore it back afterwards. This used to break error ignoring mechanism in `should_ignore`. In the last commit we updated it to use full_sequence which incidently fixed this problem. But let's restore the sequence number anyway for good measure. Signed-off-by: Yuxuan Shui --- src/event.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/event.c b/src/event.c index e6052f1de1..c4a62e8f73 100644 --- a/src/event.c +++ b/src/event.c @@ -121,11 +121,13 @@ static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) { CASESTRRET(ClientMessage); } - if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) + if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { return "Damage"; + } - if (ps->shape_exists && ev->response_type == ps->shape_event) + if (ps->shape_exists && ev->response_type == ps->shape_event) { return "ShapeNotify"; + } if (ps->xsync_exists) { int o = ev->response_type - ps->xsync_event; @@ -695,8 +697,11 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // missing sequence numbers. // // We only need the low 16 bits + uint16_t seq = ev->sequence; ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff); proc(ps->dpy, &dummy, (xEvent *)ev); + // Restore the sequence number + ev->sequence = seq; } // XXX redraw needs to be more fine grained From 3b342afa953cbb8b615d85ae1fb174597eb49224 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:47:38 +0000 Subject: [PATCH 031/177] general: fix compiler warning about unused results Signed-off-by: Yuxuan Shui --- src/dbus.c | 5 ++++- src/log.c | 2 +- src/picom.c | 6 +++++- src/utils.c | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/dbus.c b/src/dbus.c index 8b17b30d45..2b17341e58 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1435,7 +1435,10 @@ static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *ms continue; } char *tmp = NULL; - asprintf(&tmp, "\n", w->id); + if (asprintf(&tmp, "\n", w->id) < 0) { + log_fatal("Failed to allocate memory."); + abort(); + } mstrextend(&ret, tmp); free(tmp); } diff --git a/src/log.c b/src/log.c index 0b663e7702..8a494b9e9d 100644 --- a/src/log.c +++ b/src/log.c @@ -256,7 +256,7 @@ static void file_logger_write(struct log_target *tgt, const char *str, size_t le static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { auto f = (struct file_logger *)tgt; fflush(f->f); - writev(fileno(f->f), vec, vcnt); + ssize_t _ attr_unused = writev(fileno(f->f), vec, vcnt); } static void file_logger_destroy(struct log_target *tgt) { diff --git a/src/picom.c b/src/picom.c index 0e40d45a33..f922978a59 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2560,7 +2560,11 @@ int main(int argc, char **argv) { // Notify the parent that we are done. This might cause the parent // to quit, so only do this after setsid() int tmp = 1; - write(pfds[1], &tmp, sizeof tmp); + if (write(pfds[1], &tmp, sizeof tmp) != sizeof tmp) { + log_fatal("Failed to notify parent process"); + ret_code = 1; + break; + } close(pfds[1]); // We only do this once need_fork = false; diff --git a/src/utils.c b/src/utils.c index c080e53eb8..a1114a0491 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,7 +27,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, }; - writev(STDERR_FILENO, v, ARR_SIZE(v)); + ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v)); abort(); unreachable; From 4ecb8093cf68aa5b38d417744fa9047a40c62511 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 13:57:09 +0000 Subject: [PATCH 032/177] x: fix CI build failure Signed-off-by: Yuxuan Shui --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 7faa1c984a..e71f1ee5d6 100644 --- a/src/x.c +++ b/src/x.c @@ -394,7 +394,10 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { } int nrects; - auto rects = pixman_region32_rectangles(reg, &nrects); + // In older pixman versions, pixman_region32_rectangles doesn't take const + // region_t, instead of dealing with this version difference, just suppress the + // warning. + const pixman_box32_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); auto xrects = ccalloc(nrects, xcb_rectangle_t); for (int i = 0; i < nrects; i++) { xrects[i] = From aca3fdcef7bfcb1c3ce65cf87413fa6ab280d183 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 14:23:55 +0000 Subject: [PATCH 033/177] core: expand X error handling We used to have a list of X errors we should ignore in case they do occur. This commit expands that functionality to also allow us aborting on certain errors. Signed-off-by: Yuxuan Shui --- src/common.h | 44 ++++++++++++++++++------------ src/event.c | 2 +- src/picom.c | 76 ++++++++++++++++++++++++++++++++-------------------- src/picom.h | 2 +- src/x.c | 10 ++++++- src/x.h | 2 ++ 6 files changed, 87 insertions(+), 49 deletions(-) diff --git a/src/common.h b/src/common.h index c77318765b..863e14ee75 100644 --- a/src/common.h +++ b/src/common.h @@ -36,9 +36,9 @@ #include #include #include -#include #include #include +#include #include "uthash_extra.h" #ifdef CONFIG_OPENGL @@ -55,11 +55,11 @@ #include "backend/driver.h" #include "compiler.h" #include "config.h" +#include "list.h" #include "region.h" +#include "render.h" #include "types.h" #include "utils.h" -#include "list.h" -#include "render.h" #include "win_defs.h" #include "x.h" @@ -83,10 +83,17 @@ struct glx_session; struct atom; struct conv; -typedef struct _ignore { - struct _ignore *next; +enum pending_reply_action { + PENDING_REPLY_ACTION_IGNORE, + PENDING_REPLY_ACTION_ABORT, + PENDING_REPLY_ACTION_DEBUG_ABORT, +}; + +typedef struct pending_reply { + struct pending_reply *next; unsigned long sequence; -} ignore_t; + enum pending_reply_action action; +} pending_reply_t; #ifdef CONFIG_OPENGL #ifdef DEBUG_GLX_DEBUG_CONTEXT @@ -256,18 +263,18 @@ typedef struct session { /// Time of last fading. In milliseconds. long long fade_time; /// Head pointer of the error ignore linked list. - ignore_t *ignore_head; + pending_reply_t *pending_reply_head; /// Pointer to the next member of tail element of the error /// ignore linked list. - ignore_t **ignore_tail; + pending_reply_t **pending_reply_tail; // Cached blur convolution kernels. struct x_convolution_kernel **blur_kerns_cache; /// If we should quit - bool quit:1; + bool quit : 1; // TODO(yshui) use separate flags for dfferent kinds of updates so we don't // waste our time. /// Whether there are pending updates, like window creation, etc. - bool pending_updates:1; + bool pending_updates : 1; // === Expose event related === /// Pointer to an array of XRectangle-s of exposed region. @@ -468,18 +475,21 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -static void set_ignore(session_t *ps, unsigned long sequence) { - if (ps->o.show_all_xerrors) +static void set_ignore(session_t *ps, uint32_t sequence) { + if (ps->o.show_all_xerrors) { return; + } - auto i = cmalloc(ignore_t); - if (!i) - return; + auto i = cmalloc(pending_reply_t); + if (!i) { + abort(); + } i->sequence = sequence; i->next = 0; - *ps->ignore_tail = i; - ps->ignore_tail = &i->next; + i->action = PENDING_REPLY_ACTION_IGNORE; + *ps->pending_reply_tail = i; + ps->pending_reply_tail = &i->next; } /** diff --git a/src/event.c b/src/event.c index c4a62e8f73..89f9624a7c 100644 --- a/src/event.c +++ b/src/event.c @@ -665,7 +665,7 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { void ev_handle(session_t *ps, xcb_generic_event_t *ev) { if ((ev->response_type & 0x7f) != KeymapNotify) { - discard_ignore(ps, ev->full_sequence); + discard_pending(ps, ev->full_sequence); } xcb_window_t wid = ev_window(ps, ev); diff --git a/src/picom.c b/src/picom.c index f922978a59..ba97f4cc5a 100644 --- a/src/picom.c +++ b/src/picom.c @@ -282,14 +282,14 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) { // === Error handling === -void discard_ignore(session_t *ps, unsigned long sequence) { - while (ps->ignore_head) { - if (sequence > ps->ignore_head->sequence) { - ignore_t *next = ps->ignore_head->next; - free(ps->ignore_head); - ps->ignore_head = next; - if (!ps->ignore_head) { - ps->ignore_tail = &ps->ignore_head; +void discard_pending(session_t *ps, uint32_t sequence) { + while (ps->pending_reply_head) { + if (sequence > ps->pending_reply_head->sequence) { + auto next = ps->pending_reply_head->next; + free(ps->pending_reply_head); + ps->pending_reply_head = next; + if (!ps->pending_reply_head) { + ps->pending_reply_tail = &ps->pending_reply_head; } } else { break; @@ -297,13 +297,28 @@ void discard_ignore(session_t *ps, unsigned long sequence) { } } -static int should_ignore(session_t *ps, uint32_t sequence) { +static void handle_error(session_t *ps, xcb_generic_error_t *ev) { if (ps == NULL) { // Do not ignore errors until the session has been initialized - return false; + return; + } + discard_pending(ps, ev->full_sequence); + if (ps->pending_reply_head && ps->pending_reply_head->sequence == ev->full_sequence) { + if (ps->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) { + x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, + ev->minor_code, ev->error_code); + } + switch (ps->pending_reply_head->action) { + case PENDING_REPLY_ACTION_ABORT: + log_fatal("An unrecoverable X error occurred, aborting..."); + abort(); + case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break; + case PENDING_REPLY_ACTION_IGNORE: break; + } + return; } - discard_ignore(ps, sequence); - return ps->ignore_head && ps->ignore_head->sequence == sequence; + x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code, + ev->error_code); } // === Windows === @@ -964,9 +979,13 @@ void root_damaged(session_t *ps) { * Xlib error handler function. */ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, (uint32_t)ev->serial)) { - x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); - } + // Fake a xcb error, fill in just enough information + xcb_generic_error_t xcb_err; + xcb_err.full_sequence = (uint32_t)ev->serial; + xcb_err.major_code = ev->request_code; + xcb_err.minor_code = ev->minor_code; + xcb_err.error_code = ev->error_code; + handle_error(ps_g, &xcb_err); return 0; } @@ -974,10 +993,7 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { * XCB error handler function. */ void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->full_sequence)) { - x_print_error(err->full_sequence, err->major_code, err->minor_code, - err->error_code); - } + handle_error(ps, err); } /** @@ -1678,8 +1694,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, - .ignore_head = NULL, - .ignore_tail = NULL, + .pending_reply_head = NULL, + .pending_reply_tail = NULL, .quit = false, .expose_rects = NULL, @@ -1741,7 +1757,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->loop = EV_DEFAULT; pixman_region32_init(&ps->screen_reg); - ps->ignore_tail = &ps->ignore_head; + ps->pending_reply_tail = &ps->pending_reply_head; ps->o.show_all_xerrors = all_xerrors; @@ -2330,26 +2346,28 @@ static void session_destroy(session_t *ps) { // Free ignore linked list { - ignore_t *next = NULL; - for (ignore_t *ign = ps->ignore_head; ign; ign = next) { + pending_reply_t *next = NULL; + for (auto ign = ps->pending_reply_head; ign; ign = next) { next = ign->next; free(ign); } // Reset head and tail - ps->ignore_head = NULL; - ps->ignore_tail = &ps->ignore_head; + ps->pending_reply_head = NULL; + ps->pending_reply_tail = &ps->pending_reply_head; } // Free tgt_{buffer,picture} and root_picture - if (ps->tgt_buffer.pict == ps->tgt_picture) + if (ps->tgt_buffer.pict == ps->tgt_picture) { ps->tgt_buffer.pict = XCB_NONE; + } - if (ps->tgt_picture == ps->root_picture) + if (ps->tgt_picture == ps->root_picture) { ps->tgt_picture = XCB_NONE; - else + } else { free_picture(ps->c, &ps->tgt_picture); + } free_picture(ps->c, &ps->root_picture); free_paint(ps, &ps->tgt_buffer); diff --git a/src/picom.h b/src/picom.h index 7ee289bd00..b5a1e8a7b6 100644 --- a/src/picom.h +++ b/src/picom.h @@ -46,7 +46,7 @@ void cxinerama_upd_scrs(session_t *ps); void queue_redraw(session_t *ps); -void discard_ignore(session_t *ps, unsigned long sequence); +void discard_pending(session_t *ps, uint32_t sequence); void set_root_flags(session_t *ps, uint64_t flags); diff --git a/src/x.c b/src/x.c index e71f1ee5d6..e598345211 100644 --- a/src/x.c +++ b/src/x.c @@ -562,8 +562,16 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c /** * Log a X11 error */ +void x_log_error(enum log_level level, unsigned long serial, uint8_t major, + uint16_t minor, uint8_t error_code) { + if (unlikely(level >= log_get_level_tls())) { + log_printf(tls_logger, level, __func__, "%s", + _x_strerror(serial, major, minor, error_code)); + } +} + void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { - log_debug("%s", _x_strerror(serial, major, minor, error_code)); + x_log_error(LOG_LEVEL_DEBUG, serial, major, minor, error_code); } /* diff --git a/src/x.h b/src/x.h index a19288623e..78efea397c 100644 --- a/src/x.h +++ b/src/x.h @@ -231,6 +231,8 @@ void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); * Log a X11 error */ void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); +void x_log_error(enum log_level level, unsigned long serial, uint8_t major, + uint16_t minor, uint8_t error_code); /* * Convert a xcb_generic_error_t to a string that describes the error From 17b34ce3909d7e847670d6c121e1f3e5f499bf40 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 14:28:42 +0000 Subject: [PATCH 034/177] core: add set_cant_fail_cookie Enables picom to abort when certain requests fail. Signed-off-by: Yuxuan Shui --- src/common.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/common.h b/src/common.h index 863e14ee75..542dc94774 100644 --- a/src/common.h +++ b/src/common.h @@ -475,11 +475,8 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -static void set_ignore(session_t *ps, uint32_t sequence) { - if (ps->o.show_all_xerrors) { - return; - } - +static void +set_reply_action(session_t *ps, uint32_t sequence, enum pending_reply_action action) { auto i = cmalloc(pending_reply_t); if (!i) { abort(); @@ -487,7 +484,7 @@ static void set_ignore(session_t *ps, uint32_t sequence) { i->sequence = sequence; i->next = 0; - i->action = PENDING_REPLY_ACTION_IGNORE; + i->action = action; *ps->pending_reply_tail = i; ps->pending_reply_tail = &i->next; } @@ -496,7 +493,15 @@ static void set_ignore(session_t *ps, uint32_t sequence) { * Ignore X errors caused by given X request. */ static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) { - set_ignore(ps, cookie.sequence); + if (ps->o.show_all_xerrors) { + return; + } + + set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_IGNORE); +} + +static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) { + set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_ABORT); } /** From 1ea3276bd135119a1f3da67a459f05151402958c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 18 Dec 2022 19:23:06 +0000 Subject: [PATCH 035/177] x: fix missing _checked Signed-off-by: Yuxuan Shui --- src/x.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index e598345211..42bee3fce2 100644 --- a/src/x.c +++ b/src/x.c @@ -449,9 +449,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, } void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) { + assert(pict != XCB_NONE); xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE}; xcb_generic_error_t *e = xcb_request_check( - c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); + c, xcb_render_change_picture_checked(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); if (e) { log_error_x_error(e, "failed to clear clip region"); free(e); From c28462673e2b3332b355c33adbec1190572c5407 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 18 Dec 2022 19:23:49 +0000 Subject: [PATCH 036/177] backend: xrender: fix using of invalid picture when vsync is disabled Fixes #974 Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 19c2ebff8d..438197742e 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -594,13 +594,13 @@ static void present(backend_t *base, const region_t *region) { uint16_t region_width = to_u16_checked(extent->x2 - extent->x1), region_height = to_u16_checked(extent->y2 - extent->y1); - // compose() sets clip region on the back buffer, so clear it first - x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); - // limit the region of update x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region); if (xd->vsync) { + // compose() sets clip region on the back buffer, so clear it first + x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); + // Update the back buffer first, then present xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, From 37ecb4b496815115049a1844a149d6bf63811a9c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 15 Dec 2022 10:53:31 +0000 Subject: [PATCH 037/177] core: detect screen off Use the DPMS extension to detect if screen is turned off, and unredirect if it is. This also helps working around the problem where OpenGL buffers lose data when screen is turned off, causing screen to flicker later when it turns back on if use-damage is enabled. Unfortunately the DPMS extension doesn't define an event, so we have to periodically poll the screen state. Signed-off-by: Yuxuan Shui --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 3 +- src/common.h | 6 ++++ src/meson.build | 3 +- src/picom.c | 51 +++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8b65b61caa..224c4628a8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index f6bdfe1840..a23dfd0580 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * xproto * xcb * xcb-damage +* xcb-dpms * xcb-xfixes * xcb-shape * xcb-renderutil @@ -44,7 +45,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are diff --git a/src/common.h b/src/common.h index 542dc94774..2e5495e6b7 100644 --- a/src/common.h +++ b/src/common.h @@ -150,6 +150,8 @@ typedef struct session { // === Event handlers === /// ev_io for X connection ev_io xiow; + /// Timer for checking DPMS power level + ev_timer dpms_check_timer; /// Timeout for delayed unredirection. ev_timer unredir_timer; /// Timer for fading @@ -236,6 +238,8 @@ typedef struct session { xcb_sync_fence_t sync_fence; /// Whether we are rendering the first frame after screen is redirected bool first_frame; + /// Whether screen has been turned off + bool screen_is_off; // === Operation related === /// Flags related to the root window @@ -342,6 +346,8 @@ typedef struct session { int composite_error; /// Major opcode for X Composite extension. int composite_opcode; + /// Whether X DPMS extension exists + bool dpms_exists; /// Whether X Shape extension exists. bool shape_exists; /// Event base number for X Shape extension. diff --git a/src/meson.build b/src/meson.build index 09eb07b6c0..b6b24b56eb 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,7 +16,8 @@ cflags = [] required_xcb_packages = [ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb' + 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', + 'xcb-dpms', 'xcb' ] required_packages = [ diff --git a/src/picom.c b/src/picom.c index ba97f4cc5a..b5ac3a34be 100644 --- a/src/picom.c +++ b/src/picom.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,26 @@ void cxinerama_upd_scrs(session_t *ps) { free(xinerama_scrs); } +static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { + // state is a bool indicating whether dpms is enabled + return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); +} + +void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + auto ps = session_ptr(w, dpms_check_timer); + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS status."); + abort(); + } + auto now_screen_is_off = dpms_screen_is_off(r); + if (ps->screen_is_off != now_screen_is_off) { + ps->screen_is_off = now_screen_is_off; + queue_redraw(ps); + } + free(r); +} + /** * Find matched window. * @@ -920,6 +941,19 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { // If there's no window to paint, and the screen isn't redirected, // don't redirect it. unredir_possible = true; + } else if (ps->screen_is_off) { + // Screen is off, unredirect + // We do this unconditionally disregarding "unredir_if_possible" + // because it's important for correctness, because we need to + // workaround problems X server has around screen off. + // + // Known problems: + // 1. Sometimes OpenGL front buffer can lose content, and if we + // are doing partial updates (i.e. use-damage = true), the + // result will be wrong. + // 2. For frame pacing, X server sends bogus + // PresentCompleteNotify events when screen is off. + unredir_possible = true; } if (unredir_possible) { if (ps->redirected) { @@ -1800,6 +1834,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_prefetch_extension_data(ps->c, &xcb_present_id); xcb_prefetch_extension_data(ps->c, &xcb_sync_id); xcb_prefetch_extension_data(ps->c, &xcb_glx_id); + xcb_prefetch_extension_data(ps->c, &xcb_dpms_id); ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); if (!ext_info || !ext_info->present) { @@ -1868,6 +1903,21 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->glx_event = ext_info->first_event; } + ext_info = xcb_get_extension_data(ps->c, &xcb_dpms_id); + ps->dpms_exists = ext_info && ext_info->present; + if (ps->dpms_exists) { + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS info"); + goto err; + } + ps->screen_is_off = dpms_screen_is_off(r); + // Check screen status every half second + ev_timer_init(&ps->dpms_check_timer, check_dpms_status, 0, 0.5); + ev_timer_start(ps->loop, &ps->dpms_check_timer); + free(r); + } + // Parse configuration file win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}}; bool shadow_enabled = false, fading_enable = false, hasneg = false; @@ -2462,6 +2512,7 @@ static void session_destroy(session_t *ps) { // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); + ev_timer_stop(ps->loop, &ps->dpms_check_timer); ev_idle_stop(ps->loop, &ps->draw_idle); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); From df7b994d0033f891cba39a5214a2a1bf22a1ac2b Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 21 Dec 2022 23:19:51 +0300 Subject: [PATCH 038/177] x: fix leak in x_create_picture_with_pictfmt_and_pixmap --- src/x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/x.c b/src/x.c index 42bee3fce2..d00c9c2f6e 100644 --- a/src/x.c +++ b/src/x.c @@ -299,6 +299,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, free(buf); if (e) { log_error_x_error(e, "failed to create picture"); + free(e); return XCB_NONE; } return tmp_picture; From 70032b92821725ac5b4e7f988aadd39ad03c8250 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 00:20:43 +0300 Subject: [PATCH 039/177] xrender: fix leak in bind_pixmap --- src/backend/xrender/xrender.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 438197742e..d8978199bc 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -508,6 +508,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool if (!r) { log_error("Invalid pixmap: %#010x", pixmap); x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code); + free(e); return NULL; } From dd9ffecd85b9616fd4a0c740fc38fc9dbe4af548 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 01:26:51 +0300 Subject: [PATCH 040/177] xrender: fix leak in deinit and check should we actually free something fixes at least #960 --- src/backend/xrender/xrender.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index d8978199bc..f25a9218f9 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -576,9 +576,13 @@ static void deinit(backend_t *backend_data) { xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]); } xcb_render_free_picture(xd->base.c, xd->target); - for (int i = 0; i < 2; i++) { - xcb_render_free_picture(xd->base.c, xd->back[i]); - xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); + for (int i = 0; i < 3; i++) { + if (xd->back[i] != XCB_NONE) { + xcb_render_free_picture(xd->base.c, xd->back[i]); + } + if (xd->back_pixmap[i] != XCB_NONE) { + xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); + } } if (xd->present_event) { xcb_unregister_for_special_event(xd->base.c, xd->present_event); From 32020ff659806576caf5966f98ff1b716f84f40e Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 22 Dec 2022 20:40:02 +0300 Subject: [PATCH 041/177] xrender: fix leak in release_rounded_corner_cache calling wrong free function did nothing and produced ton of x errors fixes at least #892 --- src/backend/xrender/xrender.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index f25a9218f9..113d55e9b5 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -554,7 +554,7 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c assert(cache->refcount > 0); cache->refcount--; if (cache->refcount == 0) { - xcb_free_pixmap(base->c, cache->p); + xcb_render_free_picture(base->c, cache->p); free(cache); } } From cffc443bfd7960dab57802d35c649c511276fdb9 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 23 Dec 2022 15:21:52 +0300 Subject: [PATCH 042/177] xrender: make corner-radius respect inactive-dim for the new xrender backend it's enough to clip with a rounded rectangle after dimming, not before partially fixes #867 --- src/backend/xrender/xrender.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 113d55e9b5..686e6deb8c 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -265,13 +265,6 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { - // Clip tmp_pict with a rounded rectangle - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, - xrimg->rounded_rectangle->p, XCB_NONE, - tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - } - if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( @@ -294,6 +287,7 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, 0, 0, 0, 0, 0, 0, tmpw, tmph); } } + if (img->dim != 0) { // Dim the actually content of window xcb_rectangle_t rect = { @@ -307,6 +301,13 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, tmp_pict, dim_color, 1, &rect); } + if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { + // Clip tmp_pict with a rounded rectangle + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle->p, XCB_NONE, + tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, From 91828be79db2071061e9f290a8ef5915535cbf3b Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 30 Dec 2022 06:16:46 +0300 Subject: [PATCH 043/177] picom: free root image properly fixes #982 --- src/picom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/picom.c b/src/picom.c index b5ac3a34be..8705de4a92 100644 --- a/src/picom.c +++ b/src/picom.c @@ -990,6 +990,7 @@ void root_damaged(session_t *ps) { 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->root, ps->atoms); if (pixmap != XCB_NONE) { From 29342e7cead35775d6e4f68027c9338087acc7c1 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 9 Jan 2023 04:20:53 +0300 Subject: [PATCH 044/177] backend: egl: simplify getting a framebuffer configuration we're using the first available framebuffer configuration that meets our needs so there is no need to ask for more than one --- src/backend/gl/egl.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index e6d4d9018a..d61945628c 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -166,14 +166,9 @@ static backend_t *egl_init(session_t *ps) { goto end; } - int ncfgs = 0; - if (eglGetConfigs(gd->display, NULL, 0, &ncfgs) != EGL_TRUE) { - log_error("Failed to get EGL configs."); - goto end; - } - auto visual_info = x_get_visual_info(ps->c, ps->vis); - EGLConfig *cfgs = ccalloc(ncfgs, EGLConfig); + EGLConfig config = NULL; + int nconfigs = 1; // clang-format off if (eglChooseConfig(gd->display, (EGLint[]){ @@ -186,17 +181,14 @@ static backend_t *egl_init(session_t *ps) { EGL_STENCIL_SIZE, 1, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, - }, cfgs, ncfgs, &ncfgs) != EGL_TRUE) { + }, &config, nconfigs, &nconfigs) != EGL_TRUE) { log_error("Failed to choose EGL config for the root window."); goto end; } // clang-format on - EGLConfig target_cfg = cfgs[0]; - free(cfgs); - gd->target_win = eglCreatePlatformWindowSurfaceProc( - gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); + gd->display, config, (xcb_window_t[]){session_get_target_window(ps)}, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); goto end; @@ -207,7 +199,7 @@ static backend_t *egl_init(session_t *ps) { goto end; } - gd->ctx = eglCreateContext(gd->display, target_cfg, NULL, NULL); + gd->ctx = eglCreateContext(gd->display, config, NULL, NULL); if (gd->ctx == EGL_NO_CONTEXT) { log_error("Failed to get GLX context."); goto end; From ca64654256b1f62cbc564e8fa68fc80651835a60 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 10 Jan 2023 00:15:49 +0300 Subject: [PATCH 045/177] use _checked functions with xcb_request_check --- src/event.c | 3 ++- src/picom.c | 8 ++++---- src/win.c | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/event.c b/src/event.c index 89f9624a7c..082686b31b 100644 --- a/src/event.c +++ b/src/event.c @@ -283,7 +283,8 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { // in redirected state. if (ps->overlay && ev->window == ps->overlay && !ps->redirected) { log_debug("Overlay is mapped while we are not redirected"); - auto e = xcb_request_check(ps->c, xcb_unmap_window(ps->c, ps->overlay)); + auto e = + xcb_request_check(ps->c, xcb_unmap_window_checked(ps->c, ps->overlay)); if (e) { log_error("Failed to unmap the overlay window"); free(e); diff --git a/src/picom.c b/src/picom.c index b5ac3a34be..9d97a86303 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1266,7 +1266,7 @@ static bool init_debug_window(session_t *ps) { goto err_out; } - err = xcb_request_check(ps->c, xcb_map_window(ps->c, ps->debug_window)); + err = xcb_request_check(ps->c, xcb_map_window_checked(ps->c, ps->debug_window)); if (err) { goto err_out; } @@ -2069,8 +2069,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->sync_fence = XCB_NONE; if (ps->xsync_exists) { ps->sync_fence = x_new_id(ps->c); - e = xcb_request_check( - ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); + e = xcb_request_check(ps->c, xcb_sync_create_fence_checked( + ps->c, ps->root, ps->sync_fence, 0)); if (e) { if (ps->o.xrender_sync_fence) { log_error_x_error(e, "Failed to create a XSync fence. " @@ -2280,7 +2280,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_query_tree_reply_t *query_tree_reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, ps->root), NULL); - e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c)); + e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "Failed to ungrab server"); free(e); diff --git a/src/win.c b/src/win.c index 502bb82a63..cef94fe0f2 100644 --- a/src/win.c +++ b/src/win.c @@ -1299,7 +1299,7 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) } auto e = xcb_request_check( - ps->c, xcb_change_window_attributes( + ps->c, xcb_change_window_attributes_checked( ps->c, client, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)})); if (e) { From 81f2bf4c0721809cf452ded50cec65a1c5e37a46 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 10 Jan 2023 01:13:08 +0300 Subject: [PATCH 046/177] picom: fix xcb_request_check memory leaks --- src/picom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/picom.c b/src/picom.c index b5ac3a34be..8fb0a73c6b 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1463,6 +1463,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "failed to grab x server"); + free(e); return quit(ps); } @@ -1498,6 +1499,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "failed to ungrab x server"); + free(e); return quit(ps); } @@ -1822,6 +1824,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, XCB_EVENT_MASK_PROPERTY_CHANGE})); if (e) { log_error_x_error(e, "Failed to setup root window event mask"); + free(e); } xcb_prefetch_extension_data(ps->c, &xcb_render_id); From 0ecfd9fd32d92cf520c05b61378f296989b71c55 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 30 Dec 2022 07:38:28 +0300 Subject: [PATCH 047/177] backend: egl: fix creating eglpixmap according to the specification of the EGL_KHR_image_pixmap extension, if target is EGL_NATIVE_PIXMAP_KHR then ctx must be EGL_NO_CONTEXT, otherwise, the EGL_BAD_PARAMETER error is generated. source: https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_image_pixmap.txt fixes #981 --- src/backend/gl/egl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index e6d4d9018a..c83ff3c768 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -285,8 +285,9 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b eglpixmap = cmalloc(struct egl_pixmap); eglpixmap->pixmap = pixmap; - eglpixmap->image = eglCreateImageProc(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, - (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->image = + eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); eglpixmap->owned = owned; if (eglpixmap->image == EGL_NO_IMAGE) { From 74ab307626d5d5d351df64495bc43a1ec13edfac Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 11 Jan 2023 05:23:02 +0300 Subject: [PATCH 048/177] opengl: fix segfault in ensure_glx_context --- src/opengl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengl.h b/src/opengl.h index dcd8697b41..d0d37dd32a 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -159,7 +159,7 @@ static inline bool ensure_glx_context(session_t *ps) { if (!glx_has_context(ps)) glx_init(ps, false); - return ps->psglx->context; + return glx_has_context(ps); } /** From 364463feaf89fa0b7ad82f15af6576400b6daec4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 17 Jan 2023 02:56:28 +0000 Subject: [PATCH 049/177] doc: list cases that trigger unredirect unredir-if-possible doesn't just happen for fullscreen windows, be more accurate. Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index b4ff69b66d..fdf0198db0 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -134,7 +134,13 @@ OPTIONS Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window, rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy, provided that the WM supports it. *--unredir-if-possible*:: - Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows. Known to cause flickering when redirecting/unredirecting windows. + Unredirect all windows in some cases. Known to cause flickering when redirecting/unredirecting windows. Currently, unredirecting is triggered by following conditions: + * If the top level window is taking up the entire screen. In multi-monitor setup, this means ALL monitors. + * If there is no window. + * If a window is fullscreen according to its WM hints. (can be disabled with *--no-ewmh-fullscreen*). + * If a window requests to bypass the compositor ('_NET_WM_BYPASS_COMPOSITOR'). + Windows are also unredirected unconditionally when monitors are powered off, regardless if *--unredir-if-possible* is set. + *--unredir-if-possible-delay* 'MILLISECONDS':: Delay before unredirecting the window, in milliseconds. Defaults to 0. From 332501aef7f5bd4751d78612ea3a2528e63653b2 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Thu, 19 Jan 2023 22:48:45 +0100 Subject: [PATCH 050/177] backend: gl: use effective texture size for rounded corner calculation The rounded corner calculation only took the actual texture dimensions into account, effectively turning all pixels outside completely transparent. Furthermore, `texelFetch()` in the default window shader does not work with any `GL_TEXTURE_WRAP_x` parameter. As a result, a to-be-tiled root pixmap wasn't properly tiled and only visible in the original top-left corner. - Changed the default window shader to use `textureSize()` to scale the texture coordinates into the proper range for use with `texture2D()`. - Added a new uniform to the default post-processing shader containing the effective texture size for rounded corners calculation instead of relying on `textureSize()`. --- man/picom.1.asciidoc | 4 +++- src/backend/gl/gl_common.c | 5 +++++ src/backend/gl/gl_common.h | 1 + src/backend/gl/shaders.c | 6 ++++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index fdf0198db0..bb13c835d1 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -387,6 +387,7 @@ uniform float corner_radius; // corner radius of the window (pixels) uniform float border_width; // estimated border width of the window (pixels) uniform bool invert_color; // whether to invert the color of the window uniform sampler2D tex; // texture of the window +uniform vec2 effective_size; // effective dimensions of the texture (repeats pixels if larger than tex) uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0) uniform float time; // time in milliseconds, counting from an unspecified starting point @@ -412,7 +413,8 @@ vec4 default_post_processing(vec4 c); // 1) fetch the specified pixel // 2) apply default post-processing vec4 window_shader() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); + vec2 texsize = textureSize(tex, 0); + vec4 c = texture2D(tex, texcoord / texsize, 0); return default_post_processing(c); } ---- diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 9789eb51cf..6a826d8fee 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -385,6 +385,10 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe if (win_shader->uniform_tex >= 0) { glUniform1i(win_shader->uniform_tex, 0); } + if (win_shader->uniform_effective_size >= 0) { + glUniform2f(win_shader->uniform_effective_size, (float)img->ewidth, + (float)img->eheight); + } if (win_shader->uniform_dim >= 0) { glUniform1f(win_shader->uniform_dim, (float)img->dim); } @@ -596,6 +600,7 @@ static bool gl_win_shader_from_stringv(const char **vshader_strv, bind_uniform(ret, opacity); bind_uniform(ret, invert_color); bind_uniform(ret, tex); + bind_uniform(ret, effective_size); bind_uniform(ret, dim); bind_uniform(ret, brightness); bind_uniform(ret, max_brightness); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 4aad9c0d50..458abbef6f 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -35,6 +35,7 @@ typedef struct { GLint uniform_opacity; GLint uniform_invert_color; GLint uniform_tex; + GLint uniform_effective_size; GLint uniform_dim; GLint uniform_brightness; GLint uniform_max_brightness; diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 90f636b7e7..0d57a9f3e0 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -97,6 +97,7 @@ const char win_shader_glsl[] = GLSL(330, uniform bool invert_color; in vec2 texcoord; uniform sampler2D tex; + uniform vec2 effective_size; uniform sampler2D brightness; uniform float max_brightness; // Signed distance field for rectangle center at (0, 0), with size of @@ -130,7 +131,7 @@ const char win_shader_glsl[] = GLSL(330, // Using mix() to avoid a branch here. vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); - vec2 outer_size = vec2(textureSize(tex, 0)); + vec2 outer_size = effective_size; vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, inner_size / 2.0f) - corner_radius; @@ -157,7 +158,8 @@ const char win_shader_default[] = GLSL(330, uniform sampler2D tex; vec4 default_post_processing(vec4 c); vec4 window_shader() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); + vec2 texsize = textureSize(tex, 0); + vec4 c = texture2D(tex, texcoord / texsize, 0); return default_post_processing(c); } ); From 11840cb658a20a8ac570351f2eb48ea51c2d5ae6 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 21 Jan 2023 18:16:52 +0300 Subject: [PATCH 051/177] options: allow use of windows shaders for the egl backend and notify a user better if the shader interface is not available --- src/options.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/options.c b/src/options.c index e69bbb0223..238b636ee6 100644 --- a/src/options.c +++ b/src/options.c @@ -787,14 +787,13 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } if (opt->window_shader_fg || opt->window_shader_fg_rules) { - if (opt->legacy_backends || opt->backend != BKEND_GLX) { - log_warn("The new window shader interface does not work with the " - "legacy glx backend.%s", - (opt->backend == BKEND_GLX) ? " You may want to use " - "\"--glx-fshader-win\" " - "instead on the legacy " - "glx backend." - : ""); + if (opt->backend == BKEND_XRENDER || opt->legacy_backends) { + log_warn(opt->backend == BKEND_XRENDER + ? "Shader interface is not available for the " + "xrender backend." + : "The new shader interface is not available for " + "the legacy glx backend. You may want to use " + "--glx-fshader-win instead."); opt->window_shader_fg = NULL; c2_list_free(&opt->window_shader_fg_rules, free); } From 102a0bc5e0dc4cbe7ef1b326b77c3701710d375a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 29 Jan 2023 06:35:28 +0000 Subject: [PATCH 052/177] backend: mask can be null in blur() Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index 191e814609..b34b96d5d1 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -192,7 +192,7 @@ struct backend_operations { /// mask is. bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask, coord_t mask_dst, const region_t *reg_blur, - const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7); + const region_t *reg_visible) attr_nonnull(1, 3, 6, 7); /// Update part of the back buffer with the rendering buffer, then present the /// back buffer onto the target window (if not back buffered, update part of the From 986b3c1cb3b4771eb35de04e7f8b284ce781d208 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 13 Jan 2023 14:27:45 +0300 Subject: [PATCH 053/177] core: drop xinerama there are two x extensions for working with monitors (especially multiple): xinerama and randr. xinerama is old, feature-poor and in general isn't used anymore compared to the randr: new, feature-rich and widely-used. for some reason we were using both of them, so let's drop xinerama to keep things simple, clean and small. and to be modern. the drop was done in three steps: * first step was to replace all the xinerama-based code with the randr-based one and to replace or remove all the xinerama mentions; * second step was to replace the xinerama's terminology with the randr's one. xinerama was referring only to the word "screen", while randr refers to multiple words and i think the word "monitor" is the most suitable for us and, hopefully, clear both to a contributor and to an end user; * third step was to refactor the new randr-based code if needed and to address related todo's (mostly about moving related functions elsewhere). all the steps were done well except addressing a leftover todo about moving the win_update_monitor function to the x.c which wasn't done. the xinerama-shadow-crop option was renamed to crop-shadow-to-monitor, but it's previous name is still accepted, has effect and the deprecation message is printed to preserve backwards-compatibility. --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 3 +- man/picom.1.asciidoc | 4 +- picom.sample.conf | 5 +- src/backend/backend.c | 12 ++-- src/common.h | 10 ++-- src/config.c | 2 +- src/config.h | 4 +- src/config_libconfig.c | 8 ++- src/dbus.c | 2 +- src/diagnostic.c | 10 ++-- src/event.c | 4 +- src/meson.build | 3 +- src/options.c | 17 ++++-- src/picom.c | 80 ++++----------------------- src/picom.h | 2 - src/render.c | 19 ++++--- src/win.c | 44 ++++++--------- src/win.h | 10 +++- src/x.c | 38 +++++++++++++ src/x.h | 10 ++++ tests/configs/parsing_test.conf | 5 +- 22 files changed, 142 insertions(+), 152 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 224c4628a8..2e3dfe7e26 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index a23dfd0580..0fecde197e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * xcb-composite * xcb-image * xcb-present -* xcb-xinerama * xcb-glx * pixman * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) @@ -45,7 +44,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index fdf0198db0..e7a91315f2 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -217,8 +217,8 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--shadow-exclude-reg* 'GEOMETRY':: Specify a X geometry that describes the region in which shadow should not be painted in, such as a dock window region. Use `--shadow-exclude-reg x10+0-0`, for example, if the 10 pixels on the bottom of the screen should not have shadows painted on. -*--xinerama-shadow-crop*:: - Crop shadow of a window fully on a particular Xinerama screen to the screen. +*--crop-shadow-to-monitor*:: + Crop shadow of a window fully on a particular monitor to that monitor. This is currently implemented using the X RandR extension. *--backend* 'BACKEND':: Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. `xrender` is the default one. diff --git a/picom.sample.conf b/picom.sample.conf index f0c7a795d5..5d02e7d735 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -61,8 +61,9 @@ shadow-exclude = [ # # shadow-exclude-reg = "" -# Crop shadow of a window fully on a particular Xinerama screen to the screen. -# xinerama-shadow-crop = false +# Crop shadow of a window fully on a particular monitor to that monitor. This is +# currently implemented using the X RandR extension. +# crop-shadow-to-monitor = false ################################# diff --git a/src/backend/backend.c b/src/backend/backend.c index 83af8fb63c..59ddcaf2a0 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -327,18 +327,18 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_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 + if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 && + w->randr_monitor < ps->randr_nmonitors) { + // There can be a window where number of monitors is + // updated, but the monitor number attached to the window // have not. // - // Window screen number will be updated eventually, so + // Window monitor 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]); + &ps->randr_monitor_regs[w->randr_monitor]); } if (ps->o.transparent_clipping) { diff --git a/src/common.h b/src/common.h index 2e5495e6b7..8f307dc9fd 100644 --- a/src/common.h +++ b/src/common.h @@ -368,12 +368,10 @@ typedef struct session { int glx_event; /// Error base number for X GLX extension. int glx_error; - /// Whether X Xinerama extension exists. - bool xinerama_exists; - /// Xinerama screen regions. - region_t *xinerama_scr_regs; - /// Number of Xinerama screens. - int xinerama_nscrs; + /// Number of X RandR monitors. + int randr_nmonitors; + /// X RandR monitor regions. + region_t *randr_monitor_regs; /// Whether X Sync extension exists. bool xsync_exists; /// Event base number for X Sync extension. diff --git a/src/config.c b/src/config.c index d9b2dd9122..dd231470ee 100644 --- a/src/config.c +++ b/src/config.c @@ -747,7 +747,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .shadow_opacity = .75, .shadow_blacklist = NULL, .shadow_ignore_shaped = false, - .xinerama_shadow_crop = false, + .crop_shadow_to_monitor = false, .shadow_clip_list = NULL, .corner_radius = 0, diff --git a/src/config.h b/src/config.h index 3f11e1e7e1..632800c32e 100644 --- a/src/config.h +++ b/src/config.h @@ -153,8 +153,8 @@ typedef struct options { c2_lptr_t *shadow_blacklist; /// Whether bounding-shaped window should be ignored. bool shadow_ignore_shaped; - /// Whether to crop shadow to the very Xinerama screen. - bool xinerama_shadow_crop; + /// Whether to crop shadow to the very X RandR monitor. + bool crop_shadow_to_monitor; /// Don't draw shadow over these windows. A linked list of conditions. c2_lptr_t *shadow_clip_list; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index ba987a8908..589e139b63 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -384,8 +384,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped); // --detect-rounded-corners lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners); - // --xinerama-shadow-crop - lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->xinerama_shadow_crop); + // --crop-shadow-to-monitor + if (lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->crop_shadow_to_monitor)) { + log_warn("xinerama-shadow-crop is deprecated. Use crop-shadow-to-monitor " + "instead."); + } + lcfg_lookup_bool(&cfg, "crop-shadow-to-monitor", &opt->crop_shadow_to_monitor); // --detect-client-opacity lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity); // --refresh-rate diff --git a/src/dbus.c b/src/dbus.c index 2b17341e58..baff2bcc1a 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1202,7 +1202,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32); cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32); cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double); - cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool); + cdbus_m_opts_get_do(crop_shadow_to_monitor, cdbus_reply_bool); cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32); cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double); diff --git a/src/diagnostic.c b/src/diagnostic.c index 79ac5ac867..1159d9eabc 100644 --- a/src/diagnostic.c +++ b/src/diagnostic.c @@ -2,21 +2,21 @@ // Copyright (c) 2018 Yuxuan Shui #include -#include #include +#include #include "backend/driver.h" -#include "diagnostic.h" +#include "common.h" #include "config.h" +#include "diagnostic.h" #include "picom.h" -#include "common.h" void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { printf("**Version:** " PICOM_VERSION "\n"); - //printf("**CFLAGS:** %s\n", "??"); + // printf("**CFLAGS:** %s\n", "??"); printf("\n### Extensions:\n\n"); printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No"); - printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No"); + printf("* RandR: %s\n", ps->randr_exists ? "Yes" : "No"); printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present"); printf("\n### Misc:\n\n"); printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No"); diff --git a/src/event.c b/src/event.c index 082686b31b..afaa02c490 100644 --- a/src/event.c +++ b/src/event.c @@ -237,8 +237,8 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { win_set_flags(mw, WIN_FLAGS_SIZE_STALE); } - // Recalculate which screen this window is on - win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, mw); + // Recalculate which monitor this window is on + win_update_monitor(ps->randr_nmonitors, ps->randr_monitor_regs, mw); } // override_redirect flag cannot be changed after window creation, as far diff --git a/src/meson.build b/src/meson.build index b6b24b56eb..0cd87c896d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,8 +16,7 @@ cflags = [] required_xcb_packages = [ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', - 'xcb-dpms', 'xcb' + 'xcb-shape', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb-dpms', 'xcb' ] required_packages = [ diff --git a/src/options.c b/src/options.c index e69bbb0223..d2020c6ab8 100644 --- a/src/options.c +++ b/src/options.c @@ -72,6 +72,9 @@ static const struct picom_option picom_options[] = { "managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"}, {"refresh-rate" , required_argument, 269, NULL , NULL}, {"vsync" , optional_argument, 270, NULL , "Enable VSync"}, + {"crop-shadow-to-monitor" , no_argument , 271, NULL , "Crop shadow of a window fully on a particular monitor to that monitor. " + "This is currently implemented using the X RandR extension"}, + {"xinerama-shadow-crop" , no_argument , 272, NULL , NULL}, {"sw-opti" , no_argument , 274, NULL , NULL}, {"vsync-aggressive" , no_argument , 275, NULL , NULL}, {"use-ewmh-active-win" , no_argument , 276, NULL , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is " @@ -119,7 +122,6 @@ static const struct picom_option picom_options[] = { {"opacity-rule" , required_argument, 304, "OPACITY:COND", "Specify a list of opacity rules, see man page for more details"}, {"shadow-exclude-reg" , required_argument, 305, NULL , NULL}, {"paint-exclude" , required_argument, 306, NULL , NULL}, - {"xinerama-shadow-crop" , no_argument , 307, NULL , "Crop shadow of a window fully on a particular Xinerama screen to the screen."}, {"unredir-if-possible-exclude" , required_argument, 308, "COND" , "Conditions of windows that shouldn't be considered full-screen for " "unredirecting screen."}, {"unredir-if-possible-delay" , required_argument, 309, NULL, "Delay before unredirecting the window, in milliseconds. Defaults to 0."}, @@ -485,8 +487,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(267, detect_rounded_corners); P_CASEBOOL(268, detect_client_opacity); case 269: - log_warn("--refresh-rate has been deprecated, please remove it from" - "your command line options"); + log_warn("--refresh-rate has been deprecated, please remove it " + "from your command line options"); break; case 270: if (optarg) { @@ -499,6 +501,12 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->vsync = true; } break; + P_CASEBOOL(271, crop_shadow_to_monitor); + case 272: + opt->crop_shadow_to_monitor = true; + log_warn("--xinerama-shadow-crop is deprecated. Use " + "--crop-shadow-to-monitor instead."); + break; case 274: log_warn("--sw-opti has been deprecated, please remove it from the " "command line options"); @@ -506,7 +514,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 275: // --vsync-aggressive log_error("--vsync-aggressive has been removed, please remove it" - " from the command line options"); + " from the command line options"); failed = true; break; P_CASEBOOL(276, use_ewmh_active_win); @@ -625,7 +633,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --paint-exclude condlst_add(&opt->paint_blacklist, optarg); break; - P_CASEBOOL(307, xinerama_shadow_crop); case 308: // --unredir-if-possible-exclude condlst_add(&opt->unredir_if_possible_blacklist, optarg); diff --git a/src/picom.c b/src/picom.c index 149ffc5564..8a7a967039 100644 --- a/src/picom.c +++ b/src/picom.c @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -111,21 +110,6 @@ void quit(session_t *ps) { ev_break(ps->loop, EVBREAK_ALL); } -/** - * Free Xinerama screen info. - * - * XXX consider moving to x.c - */ -static inline void free_xinerama_info(session_t *ps) { - if (ps->xinerama_scr_regs) { - for (int i = 0; i < ps->xinerama_nscrs; ++i) - pixman_region32_fini(&ps->xinerama_scr_regs[i]); - free(ps->xinerama_scr_regs); - ps->xinerama_scr_regs = NULL; - } - ps->xinerama_nscrs = 0; -} - /** * Get current system clock in milliseconds. */ @@ -135,41 +119,6 @@ static inline int64_t get_time_ms(void) { return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; } -// XXX Move to x.c -void cxinerama_upd_scrs(session_t *ps) { - // XXX Consider deprecating Xinerama, switch to RandR when necessary - free_xinerama_info(ps); - - if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists) - return; - - xcb_xinerama_is_active_reply_t *active = - xcb_xinerama_is_active_reply(ps->c, xcb_xinerama_is_active(ps->c), NULL); - if (!active || !active->state) { - free(active); - return; - } - free(active); - - auto xinerama_scrs = - xcb_xinerama_query_screens_reply(ps->c, xcb_xinerama_query_screens(ps->c), NULL); - if (!xinerama_scrs) { - return; - } - - xcb_xinerama_screen_info_t *scrs = - xcb_xinerama_query_screens_screen_info(xinerama_scrs); - ps->xinerama_nscrs = xcb_xinerama_query_screens_screen_info_length(xinerama_scrs); - - ps->xinerama_scr_regs = ccalloc(ps->xinerama_nscrs, region_t); - for (int i = 0; i < ps->xinerama_nscrs; ++i) { - const xcb_xinerama_screen_info_t *const s = &scrs[i]; - pixman_region32_init_rect(&ps->xinerama_scr_regs[i], s->x_org, s->y_org, - s->width, s->height); - } - free(xinerama_scrs); -} - static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { // state is a bool indicating whether dpms is enabled return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); @@ -691,8 +640,8 @@ static void configure_root(session_t *ps) { static void handle_root_flags(session_t *ps) { if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) { - if (ps->o.xinerama_shadow_crop) { - cxinerama_upd_scrs(ps); + if (ps->o.crop_shadow_to_monitor) { + x_update_randr_monitors(ps); } ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; } @@ -1834,7 +1783,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_prefetch_extension_data(ps->c, &xcb_shape_id); xcb_prefetch_extension_data(ps->c, &xcb_xfixes_id); xcb_prefetch_extension_data(ps->c, &xcb_randr_id); - xcb_prefetch_extension_data(ps->c, &xcb_xinerama_id); xcb_prefetch_extension_data(ps->c, &xcb_present_id); xcb_prefetch_extension_data(ps->c, &xcb_sync_id); xcb_prefetch_extension_data(ps->c, &xcb_glx_id); @@ -2092,18 +2040,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } // Query X RandR - if (ps->o.xinerama_shadow_crop) { - if (!ps->randr_exists) { - log_fatal("No XRandR extension. xinerama-shadow-crop cannot be " - "enabled."); - goto err; - } - } - - // Query X Xinerama extension - if (ps->o.xinerama_shadow_crop) { - ext_info = xcb_get_extension_data(ps->c, &xcb_xinerama_id); - ps->xinerama_exists = ext_info && ext_info->present; + if (ps->o.crop_shadow_to_monitor && !ps->randr_exists) { + log_fatal("No X RandR extension. crop-shadow-to-monitor cannot be " + "enabled."); + goto err; } rebuild_screen_reg(ps); @@ -2198,12 +2138,12 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } // Monitor screen changes if vsync_sw is enabled and we are using - // an auto-detected refresh rate, or when Xinerama features are enabled - if (ps->randr_exists && ps->o.xinerama_shadow_crop) { + // an auto-detected refresh rate, or when X RandR features are enabled + if (ps->randr_exists && ps->o.crop_shadow_to_monitor) { xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); } - cxinerama_upd_scrs(ps); + x_update_randr_monitors(ps); { xcb_render_create_picture_value_list_t pa = { @@ -2436,7 +2376,7 @@ static void session_destroy(session_t *ps) { } free(ps->o.blur_kerns); free(ps->o.glx_fshader_win_str); - free_xinerama_info(ps); + x_free_randr_info(ps); // Release custom window shaders free(ps->o.window_shader_fg); diff --git a/src/picom.h b/src/picom.h index b5a1e8a7b6..6f14cd08ca 100644 --- a/src/picom.h +++ b/src/picom.h @@ -42,8 +42,6 @@ void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); void root_damaged(session_t *ps); -void cxinerama_upd_scrs(session_t *ps); - void queue_redraw(session_t *ps); void discard_pending(session_t *ps, uint32_t sequence); diff --git a/src/render.c b/src/render.c index db627de303..afde7db82b 100644 --- a/src/render.c +++ b/src/render.c @@ -1098,18 +1098,19 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { if (!ps->o.wintype_option[w->window_type].full_shadow) pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); - 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. + if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 && + w->randr_monitor < ps->randr_nmonitors) { + // There can be a window where number of monitors is + // updated, but the monitor number attached to the window + // have not. // - // Window screen number will be updated - // eventually, so here we just check to make sure - // we don't access out of bounds. + // Window monitor number will be updated eventually, so + // here we just check to make sure we don't access out of + // bounds. pixman_region32_intersect( ®_tmp, ®_tmp, - &ps->xinerama_scr_regs[w->xinerama_scr]); + &ps->randr_monitor_regs[w->randr_monitor]); + } // Detect if the region is empty before painting if (pixman_region32_not_empty(®_tmp)) { diff --git a/src/win.c b/src/win.c index cef94fe0f2..a52b4e1a37 100644 --- a/src/win.c +++ b/src/win.c @@ -14,7 +14,6 @@ #include #include #include -#include #include "atom.h" #include "backend/backend.h" @@ -517,7 +516,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { win_clear_flags(w, WIN_FLAGS_POSITION_STALE); } - win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, w); + win_update_monitor(ps->randr_nmonitors, ps->randr_monitor_regs, w); } if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) { @@ -1550,7 +1549,7 @@ struct win *fill_win(session_t *ps, struct win *w) { .shadow = false, .clip_shadow_above = false, .fg_shader = NULL, - .xinerama_scr = -1, + .randr_monitor = -1, .mode = WMODE_TRANS, .ever_damaged = false, .client_win = XCB_NONE, @@ -2434,33 +2433,24 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) { return win_check_fade_finished(ps, w); } -/** - * Get the Xinerama screen a window is on. - * - * Return an index >= 0, or -1 if not found. - * - * TODO(yshui) move to x.c - * TODO(yshui) use xrandr - */ -void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) { - w->xinerama_scr = -1; - - for (int i = 0; i < nscreens; i++) { - auto e = pixman_region32_extents(&screens[i]); - if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && - e->y2 >= w->g.y + w->heightb) { - w->xinerama_scr = i; - log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen " - "%d " - "(%dx%d+%dx%d)", - w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb, - i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); +// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to +// the x.c. +void win_update_monitor(int nmons, region_t *mons, struct managed_win *mw) { + mw->randr_monitor = -1; + for (int i = 0; i < nmons; i++) { + auto e = pixman_region32_extents(&mons[i]); + if (e->x1 <= mw->g.x && e->y1 <= mw->g.y && + e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) { + mw->randr_monitor = i; + log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the " + "monitor %d (%dx%d+%dx%d)", + mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, + mw->heightb, i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); return; } } - log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any " - "screen", - w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height); + log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor", + mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb); } /// Map an already registered window diff --git a/src/win.h b/src/win.h index abc03d9701..fe733408b5 100644 --- a/src/win.h +++ b/src/win.h @@ -123,8 +123,8 @@ struct managed_win { struct win_geometry g; /// Updated geometry received in events struct win_geometry pending_g; - /// Xinerama screen this window is on. - int xinerama_scr; + /// X RandR monitor this window is on. + int randr_monitor; /// Window visual pict format const xcb_render_pictforminfo_t *pictfmt; /// Client window visual pict format @@ -345,7 +345,11 @@ void win_recheck_client(session_t *ps, struct managed_win *w); */ double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w); bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w); -void win_update_screen(int nscreens, region_t *screens, struct managed_win *w); + +// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to +// the x.h. +void win_update_monitor(int nmons, region_t *mons, struct managed_win *mw); + /** * Retrieve the bounding shape of a window. */ diff --git a/src/x.c b/src/x.c index d00c9c2f6e..6121e77344 100644 --- a/src/x.c +++ b/src/x.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -787,3 +788,40 @@ xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) { return NULL; } + +void x_update_randr_monitors(session_t *ps) { + x_free_randr_info(ps); + + if (!ps->o.crop_shadow_to_monitor || !ps->randr_exists) { + return; + } + + xcb_randr_get_monitors_reply_t *r = xcb_randr_get_monitors_reply( + ps->c, xcb_randr_get_monitors(ps->c, ps->root, true), NULL); + if (!r) { + return; + } + + ps->randr_nmonitors = xcb_randr_get_monitors_monitors_length(r); + ps->randr_monitor_regs = ccalloc(ps->randr_nmonitors, region_t); + xcb_randr_monitor_info_iterator_t monitor_info_it = + xcb_randr_get_monitors_monitors_iterator(r); + for (int i = 0; monitor_info_it.rem; xcb_randr_monitor_info_next(&monitor_info_it)) { + xcb_randr_monitor_info_t *mi = monitor_info_it.data; + pixman_region32_init_rect(&ps->randr_monitor_regs[i++], mi->x, mi->y, + mi->width, mi->height); + } + + free(r); +} + +void x_free_randr_info(session_t *ps) { + if (ps->randr_monitor_regs) { + for (int i = 0; i < ps->randr_nmonitors; i++) { + pixman_region32_fini(&ps->randr_monitor_regs[i]); + } + free(ps->randr_monitor_regs); + ps->randr_monitor_regs = NULL; + } + ps->randr_nmonitors = 0; +} diff --git a/src/x.h b/src/x.h index 78efea397c..fc105c7857 100644 --- a/src/x.h +++ b/src/x.h @@ -302,4 +302,14 @@ x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); +/** + * X RandR-related functions. + * + * The x_update_randr_monitors function populates ps->randr_nmonitors and + * ps->randr_monitor_regs with the data X RandR provided and the + * x_free_randr_info function frees them. + */ +void x_update_randr_monitors(session_t *ps); +void x_free_randr_info(session_t *ps); + uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index 72360173a8..2476d49a6c 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -64,8 +64,9 @@ shadow-exclude = [ # # shadow-exclude-reg = "" -# Crop shadow of a window fully on a particular Xinerama screen to the screen. -# xinerama-shadow-crop = false +# Crop shadow of a window fully on a particular monitor to that monitor. This is +# currently implemented using the X RandR extension. +# crop-shadow-to-monitor = false ################################# From cee12875625465292bc11bf09dc8ab117cae75f4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 29 Jan 2023 16:18:58 +0000 Subject: [PATCH 054/177] github: update git-clang-format-lint action Signed-off-by: Yuxuan Shui --- .github/workflows/coding-style-pr.yml | 2 +- .github/workflows/coding-style.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-style-pr.yml b/.github/workflows/coding-style-pr.yml index e25eaff9dd..c667e3f6fb 100644 --- a/.github/workflows/coding-style-pr.yml +++ b/.github/workflows/coding-style-pr.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v2 - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} - - uses: yshui/git-clang-format-lint@v1.13 + - uses: yshui/git-clang-format-lint@v1.14 with: base: ${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 0086225127..d378113c4d 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -9,6 +9,6 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 2 - - uses: yshui/git-clang-format-lint@v1.13 + - uses: yshui/git-clang-format-lint@v1.14 with: base: ${{ github.event.ref }}~1 From ce7758a07b93a48fb272f22695fe421b07e70ace Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 13 Feb 2023 02:23:53 +0300 Subject: [PATCH 055/177] backend: fix leak in default_backend_render_shadow --- src/backend/backend_common.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index d6fcce21ce..ecdefa4cf5 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -294,13 +294,14 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i void *default_backend_render_shadow(backend_t *backend_data, int width, int height, struct backend_shadow_context *sctx, struct color color) { const conv *kernel = (void *)sctx; - xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true, - 1, color.red, color.green, color.blue), - shadow = XCB_NONE; + xcb_render_picture_t shadow_pixel = solid_picture( + backend_data->c, backend_data->root, true, 1, color.red, color.green, color.blue); + xcb_pixmap_t shadow = XCB_NONE; xcb_render_picture_t pict = XCB_NONE; if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height, kernel, shadow_pixel, &shadow, &pict)) { + xcb_render_free_picture(backend_data->c, shadow_pixel); return NULL; } @@ -308,6 +309,7 @@ void *default_backend_render_shadow(backend_t *backend_data, int width, int heig void *ret = backend_data->ops->bind_pixmap( backend_data, shadow, x_get_visual_info(backend_data->c, visual), true); xcb_render_free_picture(backend_data->c, pict); + xcb_render_free_picture(backend_data->c, shadow_pixel); return ret; } From 6d459badbcb6b31ae622df329fa663b5b3293f48 Mon Sep 17 00:00:00 2001 From: oofsauce Date: Wed, 29 Mar 2023 21:18:41 +0100 Subject: [PATCH 056/177] Add corner-radius-rules configuration option This option accepts a list of patterns and overrides the corner radii of matching windows Authored-by: oofsauce Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 3 +++ src/config.c | 23 ++++++++++++++--------- src/config.h | 4 +++- src/config_libconfig.c | 37 +++++++++++++++++++++++++++++++++---- src/options.c | 9 ++++++++- src/win.c | 19 +++++++++++++++---- 6 files changed, 76 insertions(+), 19 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index e7a91315f2..0a2e3c31a5 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -103,6 +103,9 @@ OPTIONS *--corner-radius* 'VALUE':: Sets the radius of rounded window corners. When > 0, the compositor will round the corners of windows. Does not interact well with *--transparent-clipping*. (defaults to 0). +*--corner-radius-rules* 'RADIUS':'CONDITION':: + Specify a list of corner radius rules. Overrides the corner radii of matching windows. This option takes precedence over the *--rounded-corners-exclude* option, and also overrides the default exclusion of fullscreen windows. The condition has the same format as *--opacity-rule*. + *--rounded-corners-exclude* 'CONDITION':: Exclude conditions for rounded corners. diff --git a/src/config.c b/src/config.c index dd231470ee..1013d128fa 100644 --- a/src/config.c +++ b/src/config.c @@ -509,32 +509,37 @@ bool parse_geometry(session_t *ps, const char *src, region_t *dest) { } /** - * Parse a list of opacity rules. + * Parse a list of window rules, prefixed with a number, separated by a ':' */ -bool parse_rule_opacity(c2_lptr_t **res, const char *src) { - // Find opacity value +bool parse_numeric_window_rule(c2_lptr_t **res, const char *src, long min, long max) { + if (!src) { + return false; + } + + // Find numeric value char *endptr = NULL; long val = strtol(src, &endptr, 0); if (!endptr || endptr == src) { - log_error("No opacity specified: %s", src); + log_error("No number specified: %s", src); return false; } - if (val > 100 || val < 0) { - log_error("Opacity %ld invalid: %s", val, src); + + if (val < min || val > max) { + log_error("Number not in range (%ld <= n <= %ld): %s", min, max, src); return false; } // Skip over spaces - while (*endptr && isspace((unsigned char)*endptr)) + while (*endptr && isspace((unsigned char)*endptr)) { ++endptr; + } if (':' != *endptr) { - log_error("Opacity terminator not found: %s", src); + log_error("Number separator (':') not found: %s", src); return false; } ++endptr; // Parse pattern - // I hope 1-100 is acceptable for (void *) return c2_parse(res, endptr, (void *)val); } diff --git a/src/config.h b/src/config.h index 632800c32e..5ce3ba9635 100644 --- a/src/config.h +++ b/src/config.h @@ -228,6 +228,8 @@ typedef struct options { int corner_radius; /// Rounded corners blacklist. A linked list of conditions. c2_lptr_t *rounded_corners_blacklist; + /// Rounded corner rules. A linked list of conditions. + c2_lptr_t *corner_radius_rules; // === Focus related === /// Whether to try to detect WM windows and mark them as focused. @@ -266,7 +268,7 @@ bool must_use parse_long(const char *, long *); bool must_use parse_int(const char *, int *); struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count); bool must_use parse_geometry(session_t *, const char *, region_t *); -bool must_use parse_rule_opacity(c2_lptr_t **, const char *); +bool must_use parse_numeric_window_rule(c2_lptr_t **, const char *, long, long); bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *); char *must_use locate_auxiliary_file(const char *scope, const char *path, const char *include_dir); diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 589e139b63..63f05412bf 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -135,6 +135,32 @@ void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *n } } +/** + * Parse a window corner radius rule list in configuration file. + */ +static inline void +parse_cfg_condlst_corner(options_t *opt, const config_t *pcfg, const char *name) { + config_setting_t *setting = config_lookup(pcfg, name); + if (setting) { + // Parse an array of options + if (config_setting_is_array(setting)) { + int i = config_setting_length(setting); + while (i--) + if (!parse_numeric_window_rule( + &opt->corner_radius_rules, + config_setting_get_string_elem(setting, i), 0, INT_MAX)) + exit(1); + } + // Treat it as a single pattern if it's a string + else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { + if (!parse_numeric_window_rule(&opt->corner_radius_rules, + config_setting_get_string(setting), + 0, INT_MAX)) + exit(1); + } + } +} + /** * Parse an opacity rule list in configuration file. */ @@ -146,15 +172,15 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) { if (config_setting_is_array(setting)) { int i = config_setting_length(setting); while (i--) - if (!parse_rule_opacity( + if (!parse_numeric_window_rule( &opt->opacity_rules, - config_setting_get_string_elem(setting, i))) + config_setting_get_string_elem(setting, i), 0, 100)) exit(1); } // Treat it as a single pattern if it's a string else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { - if (!parse_rule_opacity(&opt->opacity_rules, - config_setting_get_string(setting))) + if (!parse_numeric_window_rule( + &opt->opacity_rules, config_setting_get_string(setting), 0, 100)) exit(1); } } @@ -334,6 +360,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad config_lookup_int(&cfg, "corner-radius", &opt->corner_radius); // --rounded-corners-exclude parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude"); + // --corner-radius-rules + parse_cfg_condlst_corner(opt, &cfg, "corner-radius-rules"); + // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) diff --git a/src/options.c b/src/options.c index d2020c6ab8..9d500b8e65 100644 --- a/src/options.c +++ b/src/options.c @@ -161,6 +161,7 @@ static const struct picom_option picom_options[] = { {"corner-radius" , required_argument, 333, NULL , "Sets the radius of rounded window corners. When > 0, the compositor will " "round the corners of windows. (defaults to 0)."}, {"rounded-corners-exclude" , required_argument, 334, "COND" , "Exclude conditions for rounded corners."}, + {"corner-radius-rules" , required_argument, 340, "RADIUS:COND" , "Window rules for specific rounded corner radii."}, {"clip-shadow-above" , required_argument, 335, NULL , "Specify a list of conditions of windows to not paint a shadow over, such " "as a dock window."}, {"window-shader-fg" , required_argument, 336, "PATH" , "Specify GLSL fragment shader path for rendering window contents. Does not" @@ -174,6 +175,7 @@ static const struct picom_option picom_options[] = { {"dithered-present" , no_argument , 339, NULL , "Use higher precision during rendering, and apply dither when presenting the " "rendered screen. Reduces banding artifacts, but might cause performance " "degradation. Only works with OpenGL."}, + // 340 is corner-radius-rules {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, @@ -619,7 +621,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; case 304: // --opacity-rule - if (!parse_rule_opacity(&opt->opacity_rules, optarg)) + if (!parse_numeric_window_rule(&opt->opacity_rules, optarg, 0, 100)) exit(1); break; case 305: @@ -723,6 +725,11 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --rounded-corners-exclude condlst_add(&opt->rounded_corners_blacklist, optarg); break; + case 340: + // --corner-radius-rules + if (!parse_numeric_window_rule(&opt->corner_radius_rules, optarg, 0, INT_MAX)) + exit(1); + break; case 335: // --clip-shadow-above condlst_add(&opt->shadow_clip_list, optarg); diff --git a/src/win.c b/src/win.c index a52b4e1a37..a97733dbd4 100644 --- a/src/win.c +++ b/src/win.c @@ -1139,13 +1139,24 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) return; } - // Don't round full screen windows & excluded windows - if ((w && win_is_fullscreen(ps, w)) || - c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + void *radius_override = NULL; + if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) { + log_debug("Matched corner rule! %d", w->corner_radius); + } + + // Don't round full screen windows & excluded windows, + // unless we find a corner override in corner_radius_rules + if (!radius_override && ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL))) { w->corner_radius = 0; log_debug("Not rounding corners for window %#010x", w->base.id); } else { - w->corner_radius = ps->o.corner_radius; + if (radius_override) { + w->corner_radius = (int)(long)radius_override; + } else { + w->corner_radius = ps->o.corner_radius; + } + log_debug("Rounding corners for window %#010x", w->base.id); // Initialize the border color to an invalid value w->border_col[0] = w->border_col[1] = w->border_col[2] = From 379f67f5c289ec84ca712c3ea66a2ccc440bee7f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 1 Apr 2023 11:27:05 +0100 Subject: [PATCH 057/177] tests: add corner-radius-rules to the parsing test Signed-off-by: Yuxuan Shui --- tests/configs/parsing_test.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index 2476d49a6c..7b6acd310a 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -155,6 +155,11 @@ rounded-corners-exclude = [ "window_type = 'desktop'" ]; +corner-radius-rules = [ + "10:window_type = 'dock'", + "0x32:window_type = 'desktop'" +]; + ################################# # Background-Blurring # From 3aed5599c3f73cbfa53b0249795e76ab07cf9ecd Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 8 May 2023 14:58:20 +0100 Subject: [PATCH 058/177] glx: calculate residual in both directions We only considered residual in the positive direction, i.e. we would set dithering to zero if the color is (whole number + 0.0001). But we should also set dithering to zero if color is (whole number - 0.0001). Fixes #1064 --- src/backend/gl/shaders.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 90f636b7e7..96e385b192 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -202,7 +202,8 @@ const char dither_glsl[] = GLSL(330, } vec4 dither(vec4 c, vec2 coord) { vec4 residual = mod(c, 1.0 / 255.0); - vec4 dithered = vec4(greaterThan(residual, vec4(1e-4))); + residual = min(residual, vec4(1.0 / 255.0) - residual); + vec4 dithered = vec4(greaterThan(residual, vec4(1.0 / 65535.0))); return vec4(c + dithered * bayer(coord) / 255.0); } ); From 62fcfe5d1a67ea88ad7356964153c31218b7c62d Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 12 Dec 2022 10:41:54 +0000 Subject: [PATCH 059/177] backend: remove unused parameter 'ignore_damage' Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 9 ++------- src/backend/backend.h | 3 +-- src/picom.c | 4 ++-- src/render.c | 4 ++-- src/render.h | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index 59ddcaf2a0..3543ee127e 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -81,7 +81,7 @@ void handle_device_reset(session_t *ps) { } /// paint all windows -void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { +void paint_all_new(session_t *ps, struct managed_win *t) { if (ps->backend_data->ops->device_status && ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) { return handle_device_reset(ps); @@ -100,12 +100,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // 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); - } + reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage); if (!pixman_region32_not_empty(®_damage)) { pixman_region32_fini(®_damage); diff --git a/src/backend/backend.h b/src/backend/backend.h index b34b96d5d1..ef1bcb8800 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -363,5 +363,4 @@ struct backend_operations { extern struct backend_operations *backend_list[]; -void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) - attr_nonnull(1); +void paint_all_new(session_t *ps, struct managed_win *const t) attr_nonnull(1); diff --git a/src/picom.c b/src/picom.c index 8a7a967039..d31c6634d1 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1524,9 +1524,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { log_trace("Render start, frame %d", paint); if (!ps->o.legacy_backends) { - paint_all_new(ps, bottom, false); + paint_all_new(ps, bottom); } else { - paint_all(ps, bottom, false); + paint_all(ps, bottom); } log_trace("Render end"); diff --git a/src/render.c b/src/render.c index afde7db82b..80cb59a6fa 100644 --- a/src/render.c +++ b/src/render.c @@ -969,7 +969,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t /// paint all windows /// region = ?? /// region_real = the damage region -void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { +void paint_all(session_t *ps, struct managed_win *t) { if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) { if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) { log_error("x_fence_sync failed, xrender-sync-fence will be " @@ -984,7 +984,7 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { region_t region; pixman_region32_init(®ion); int buffer_age = get_buffer_age(ps); - if (buffer_age == -1 || buffer_age > ps->ndamage || ignore_damage) { + if (buffer_age == -1 || buffer_age > ps->ndamage) { pixman_region32_copy(®ion, &ps->screen_reg); } else { for (int i = 0; i < get_buffer_age(ps); i++) { diff --git a/src/render.h b/src/render.h index 95a46dbee0..249f7bf255 100644 --- a/src/render.h +++ b/src/render.h @@ -37,7 +37,7 @@ void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw const glx_prog_main_t *pprogram, clip_t *clip); void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); -void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); +void paint_all(session_t *ps, struct managed_win *const t); void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); From 64a97b57b5c872555c0a8f74f8e83551618c4eb5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 13 Dec 2022 20:31:32 +0000 Subject: [PATCH 060/177] core: collect frame timing information. Needed to estimate refresh rate, and for us to stay in phase with vblank. Signed-off-by: Yuxuan Shui --- src/common.h | 9 ++++++ src/picom.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 4 +-- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/common.h b/src/common.h index 8f307dc9fd..99ccc8e17c 100644 --- a/src/common.h +++ b/src/common.h @@ -240,6 +240,15 @@ typedef struct session { bool first_frame; /// Whether screen has been turned off bool screen_is_off; + /// Event context for X Present extension. + uint32_t present_event_id; + xcb_special_event_t *present_event; + /// Last MSC event, in useconds. + uint64_t last_msc; + /// When did we render our last frame. + uint64_t last_render; + + struct rolling_avg *frame_time; // === Operation related === /// Flags related to the root window diff --git a/src/picom.c b/src/picom.c index d31c6634d1..b37ea54da3 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1291,6 +1291,24 @@ static bool redirect_start(session_t *ps) { pixman_region32_init(&ps->damage_ring[i]); } + if (ps->present_exists) { + ps->present_event_id = x_new_id(ps->c); + auto select_input = xcb_present_select_input( + ps->c, ps->present_event_id, session_get_target_window(ps), + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); + auto notify_msc = + xcb_present_notify_msc(ps->c, session_get_target_window(ps), 0, 0, 1, 0); + set_cant_fail_cookie(ps, select_input); + set_cant_fail_cookie(ps, notify_msc); + ps->present_event = xcb_register_for_special_xge( + ps->c, &xcb_present_id, ps->present_event_id, NULL); + + // Initialize rendering and frame timing statistics. + ps->last_msc = 0; + ps->last_render = 0; + rolling_avg_reset(ps->frame_time); + } + // Must call XSync() here x_sync(ps->c); @@ -1332,6 +1350,14 @@ static void unredirect(session_t *ps) { free(ps->damage_ring); ps->damage_ring = ps->damage = NULL; + if (ps->present_event_id) { + xcb_present_select_input(ps->c, ps->present_event_id, + session_get_target_window(ps), 0); + ps->present_event_id = XCB_NONE; + xcb_unregister_for_special_event(ps->c, ps->present_event); + ps->present_event = NULL; + } + // Must call XSync() here x_sync(ps->c); @@ -1339,9 +1365,53 @@ static void unredirect(session_t *ps) { log_debug("Screen unredirected."); } +static void +handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_t *cne) { + if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) { + return; + } + if (cne->ust <= ps->last_msc) { + return; + } + + auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), 0, + cne->msc + 1, 0, 0); + set_cant_fail_cookie(ps, cookie); + + if (ps->last_msc != 0) { + int frame_time = (int)(cne->ust - ps->last_msc); + rolling_avg_push(ps->frame_time, frame_time); + log_trace("Frame time: %d us, rolling average: %lf us, msc: %" PRIu64, + frame_time, rolling_avg_get_avg(ps->frame_time), cne->ust); + } + ps->last_msc = cne->ust; +} + +static void handle_present_events(session_t *ps) { + if (!ps->present_event) { + return; + } + xcb_present_generic_event_t *ev; + while ((ev = (void *)xcb_poll_for_special_event(ps->c, ps->present_event))) { + if (ev->event != ps->present_event_id) { + // This event doesn't have the right event context, it's not meant + // for us. + goto next; + } + + // We only subscribed to the complete notify event. + assert(ev->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY); + handle_present_complete_notify(ps, (void *)ev); + next: + free(ev); + } +} + // Handle queued events before we go to sleep static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) { session_t *ps = session_ptr(w, event_check); + handle_present_events(ps); + xcb_generic_event_t *ev; while ((ev = xcb_poll_for_queued_event(ps->c))) { ev_handle(ps, ev); @@ -1697,6 +1767,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .white_picture = XCB_NONE, .shadow_context = NULL, + .last_msc = 0, + #ifdef CONFIG_VSYNC_DRM .drm_fd = -1, #endif @@ -1716,6 +1788,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .randr_exists = 0, .randr_event = 0, .randr_error = 0, + .present_event_id = XCB_NONE, .glx_exists = false, .glx_event = 0, .glx_error = 0, @@ -1743,6 +1816,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->loop = EV_DEFAULT; pixman_region32_init(&ps->screen_reg); + // TODO(yshui) investigate what's the best window size + ps->frame_time = rolling_avg_new(16); + ps->pending_reply_tail = &ps->pending_reply_head; ps->o.show_all_xerrors = all_xerrors; @@ -2378,6 +2454,8 @@ static void session_destroy(session_t *ps) { free(ps->o.glx_fshader_win_str); x_free_randr_info(ps); + rolling_avg_destroy(ps->frame_time); + // Release custom window shaders free(ps->o.window_shader_fg); struct shader_info *shader, *tmp; diff --git a/src/utils.h b/src/utils.h index 5fa92199bb..b7c452912e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -292,14 +292,14 @@ int next_power_of_two(int n); struct rolling_max; struct rolling_max *rolling_max_new(int window_size); -void rolling_max_free(struct rolling_max *rm); +void rolling_max_destroy(struct rolling_max *rm); void rolling_max_reset(struct rolling_max *rm); void rolling_max_push(struct rolling_max *rm, int val); int rolling_max_get_max(struct rolling_max *rm); struct rolling_avg; struct rolling_avg *rolling_avg_new(int window_size); -void rolling_avg_free(struct rolling_avg *ra); +void rolling_avg_destroy(struct rolling_avg *ra); void rolling_avg_reset(struct rolling_avg *ra); void rolling_avg_push(struct rolling_avg *ra, int val); double rolling_avg_get_avg(struct rolling_avg *ra); From 2a4e32babf14564e9b0658be5765dc577a1164ea Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Dec 2022 08:07:36 +0000 Subject: [PATCH 061/177] backend: add last_render_time interface. Used for querying how long render takes to complete, used for frame pacing. Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 8 ++++++++ src/backend/gl/egl.c | 2 ++ src/backend/gl/gl_common.c | 31 +++++++++++++++++++++++++++++++ src/backend/gl/gl_common.h | 4 ++++ src/backend/gl/glx.c | 2 ++ src/common.h | 1 + src/picom.c | 15 +++++++++++++++ 7 files changed, 63 insertions(+) diff --git a/src/backend/backend.h b/src/backend/backend.h index ef1bcb8800..7cd64a082b 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -292,6 +292,14 @@ struct backend_operations { /// Optional int (*buffer_age)(backend_t *backend_data); + /// Get the render time of the last frame. If the render is still in progress, + /// returns false. The time is returned in `ts`. Frames are delimited by the + /// present() calls. i.e. after a present() call, last_render_time() should start + /// reporting the time of the just presen1ted frame. + /// + /// Optional, if not available, the most conservative estimation will be used. + bool (*last_render_time)(backend_t *backend_data, struct timespec *ts); + /// The maximum number buffer_age might return. int max_buffer_age; diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index dd1d4037c3..770bd5d4ab 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -372,6 +372,7 @@ struct backend_operations egl_ops = { .deinit = egl_deinit, .bind_pixmap = egl_bind_pixmap, .release_image = gl_release_image, + .prepare = gl_prepare, .compose = gl_compose, .image_op = gl_image_op, .set_image_property = gl_set_image_property, @@ -380,6 +381,7 @@ struct backend_operations egl_ops = { .is_image_transparent = default_is_image_transparent, .present = egl_present, .buffer_age = egl_buffer_age, + .last_render_time = gl_last_render_time, .create_shadow_context = gl_create_shadow_context, .destroy_shadow_context = gl_destroy_shadow_context, .render_shadow = backend_render_shadow_from_mask, diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 9789eb51cf..b02572dfad 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -22,6 +22,11 @@ #include "backend/backend_common.h" #include "backend/gl/gl_common.h" +void gl_prepare(backend_t *base, const region_t *reg attr_unused) { + auto gd = (struct gl_data *)base; + glBeginQuery(GL_TIME_ELAPSED, gd->frame_timing[gd->current_frame_timing]); +} + GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { log_trace("===\n%s\n===", shader_str); @@ -800,6 +805,9 @@ uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *sha } bool gl_init(struct gl_data *gd, session_t *ps) { + glGenQueries(2, gd->frame_timing); + gd->current_frame_timing = 0; + // Initialize GLX data structure glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); @@ -1154,10 +1162,33 @@ void gl_present(backend_t *base, const region_t *region) { glDeleteBuffers(2, bo); glDeleteVertexArrays(1, &vao); + glEndQuery(GL_TIME_ELAPSED); + gd->current_frame_timing ^= 1; + + gl_check_err(); + free(coord); free(indices); } +bool gl_last_render_time(backend_t *base, struct timespec *ts) { + auto gd = (struct gl_data *)base; + GLint available = 0; + glGetQueryObjectiv(gd->frame_timing[gd->current_frame_timing ^ 1], + GL_QUERY_RESULT_AVAILABLE, &available); + if (!available) { + return false; + } + + GLuint64 time; + glGetQueryObjectui64v(gd->frame_timing[gd->current_frame_timing ^ 1], + GL_QUERY_RESULT, &time); + ts->tv_sec = (long)(time / 1000000000); + ts->tv_nsec = (long)(time % 1000000000); + gl_check_err(); + return true; +} + bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) { struct backend_image *tex = image_data; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 4aad9c0d50..f7e721c289 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -108,6 +108,8 @@ struct gl_data { gl_shadow_shader_t shadow_shader; GLuint back_texture, back_fbo; GLint back_format; + GLuint frame_timing[2]; + int current_frame_timing; GLuint present_prog; bool dithered_present; @@ -129,6 +131,7 @@ typedef struct session session_t; #define GL_PROG_MAIN_INIT \ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } +void gl_prepare(backend_t *base, const region_t *reg); void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int extent_height, int texture_height, int root_height, bool y_inverted, GLint *coord, GLuint *indices); @@ -142,6 +145,7 @@ void gl_destroy_window_shader(backend_t *backend_data, void *shader); uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader); bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, void *image_data, void *args); +bool gl_last_render_time(backend_t *backend_data, struct timespec *time); /** * @brief Render a region with texture data. diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 109bec9429..b4ace13388 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -528,6 +528,7 @@ struct backend_operations glx_ops = { .deinit = glx_deinit, .bind_pixmap = glx_bind_pixmap, .release_image = gl_release_image, + .prepare = gl_prepare, .compose = gl_compose, .image_op = gl_image_op, .set_image_property = gl_set_image_property, @@ -536,6 +537,7 @@ struct backend_operations glx_ops = { .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, + .last_render_time = gl_last_render_time, .create_shadow_context = gl_create_shadow_context, .destroy_shadow_context = gl_destroy_shadow_context, .render_shadow = backend_render_shadow_from_mask, diff --git a/src/common.h b/src/common.h index 99ccc8e17c..1684b6c27f 100644 --- a/src/common.h +++ b/src/common.h @@ -249,6 +249,7 @@ typedef struct session { uint64_t last_render; struct rolling_avg *frame_time; + struct rolling_max *render_stats; // === Operation related === /// Flags related to the root window diff --git a/src/picom.c b/src/picom.c index b37ea54da3..7d5ab2f2ec 100644 --- a/src/picom.c +++ b/src/picom.c @@ -159,6 +159,19 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t void queue_redraw(session_t *ps) { // If --benchmark is used, redraw is always queued if (!ps->redraw_needed && !ps->o.benchmark) { + if (!ps->first_frame && ps->redirected && + ps->backend_data->ops->last_render_time) { + struct timespec render_time; + if (ps->backend_data->ops->last_render_time(ps->backend_data, + &render_time)) { + int render_time_us = (int)(render_time.tv_sec * 1000000 + + render_time.tv_nsec / 1000); + log_info( + "Last render call took: %d us, rolling_max: %d us", + render_time_us, rolling_max_get_max(ps->render_stats)); + rolling_max_push(ps->render_stats, render_time_us); + } + } ev_idle_start(ps->loop, &ps->draw_idle); } ps->redraw_needed = true; @@ -1817,6 +1830,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, pixman_region32_init(&ps->screen_reg); // TODO(yshui) investigate what's the best window size + ps->render_stats = rolling_max_new(128); ps->frame_time = rolling_avg_new(16); ps->pending_reply_tail = &ps->pending_reply_head; @@ -2455,6 +2469,7 @@ static void session_destroy(session_t *ps) { x_free_randr_info(ps); rolling_avg_destroy(ps->frame_time); + rolling_max_destroy(ps->render_stats); // Release custom window shaders free(ps->o.window_shader_fg); From 86d3739374a9cdc0f81f10ba3a6599fa40747dd5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 13 Dec 2022 20:56:11 +0000 Subject: [PATCH 062/177] core: frame pacing Use frame timing and render time statistic to pace frames. Right now the criteria are simple: * Don't render multiple frames in one vblank cycle. Otherwise the rendered frame will be delay multiple cycles, which isn't ideal. * Start rendering as late as possible while still hitting vblank. Refresh rate is estimated from a rolling average of frame timing. Render time is predicted from the rolling maximum of past 128 frames. The window size still needs to be investigated. Remove glFinish calls and GL_MaxFramesAllowed=1, frame pacing superseeds them. Professionals might laugh at how rudimentary this is, but hopefully this is better than what we had before. Which is absolutely nothing at all. Signed-off-by: Yuxuan Shui --- src/backend/driver.c | 2 - src/backend/gl/egl.c | 3 - src/backend/gl/gl_common.c | 2 +- src/backend/gl/glx.c | 3 - src/common.h | 7 +- src/picom.c | 178 +++++++++++++++++++++++++++++++------ 6 files changed, 156 insertions(+), 39 deletions(-) diff --git a/src/backend/driver.c b/src/backend/driver.c index a41d2fdc1f..b909a62272 100644 --- a/src/backend/driver.c +++ b/src/backend/driver.c @@ -15,8 +15,6 @@ /// Apply driver specified global workarounds. It's safe to call this multiple times. void apply_driver_workarounds(struct session *ps, enum driver driver) { if (driver & DRIVER_NVIDIA) { - // setenv("__GL_YIELD", "usleep", true); - setenv("__GL_MaxFramesAllowed", "1", true); ps->o.xrender_sync_fence = true; } } diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 770bd5d4ab..f1f67d91e6 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -320,9 +320,6 @@ static void egl_present(backend_t *base, const region_t *region attr_unused) { struct egl_data *gd = (void *)base; gl_present(base, region); eglSwapBuffers(gd->display, gd->target_win); - if (!gd->gl.is_nvidia) { - glFinish(); - } } static int egl_buffer_age(backend_t *base) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index b02572dfad..42f7136dc6 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -953,7 +953,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) { const char *vendor = (const char *)glGetString(GL_VENDOR); log_debug("GL_VENDOR = %s", vendor); if (strcmp(vendor, "NVIDIA Corporation") == 0) { - log_info("GL vendor is NVIDIA, don't use glFinish"); + log_info("GL vendor is NVIDIA, enable xrender sync fence."); gd->is_nvidia = true; } else { gd->is_nvidia = false; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index b4ace13388..80056c6798 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -468,9 +468,6 @@ static void glx_present(backend_t *base, const region_t *region attr_unused) { struct _glx_data *gd = (void *)base; gl_present(base, region); glXSwapBuffers(gd->display, gd->target_win); - if (!gd->gl.is_nvidia) { - glFinish(); - } } static int glx_buffer_age(backend_t *base) { diff --git a/src/common.h b/src/common.h index 1684b6c27f..e6bbff0624 100644 --- a/src/common.h +++ b/src/common.h @@ -156,9 +156,8 @@ typedef struct session { ev_timer unredir_timer; /// Timer for fading ev_timer fade_timer; - /// Use an ev_idle callback for drawing - /// So we only start drawing when events are processed - ev_idle draw_idle; + /// Use an ev_timer callback for drawing + ev_timer draw_timer; /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue @@ -247,6 +246,8 @@ typedef struct session { uint64_t last_msc; /// When did we render our last frame. uint64_t last_render; + /// Whether we can perform frame pacing. + bool frame_pacing; struct rolling_avg *frame_time; struct rolling_max *render_stats; diff --git a/src/picom.c b/src/picom.c index 7d5ab2f2ec..b5867413e9 100644 --- a/src/picom.c +++ b/src/picom.c @@ -156,23 +156,87 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t return w; } +/// How many seconds into the future should we start rendering the next frame. +double next_frame_offset(session_t *ps) { + int render_time = rolling_max_get_max(ps->render_stats); + if (render_time < 0) { + // We don't have render time estimates, maybe there's no frame rendered + // yet, or the backend doesn't support render timing information, + // queue render immediately. + return 0; + } + int frame_time = (int)rolling_avg_get_avg(ps->frame_time); + auto next_msc = ps->last_msc + (uint64_t)frame_time; + auto deadline = next_msc - (uint64_t)render_time; + + const uint64_t slack = 1000; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; + if (now_us + slack >= deadline) { + // We are already late, render immediately. + log_trace("Already late, rendering immediately, last_msc: %" PRIu64 + ", render_time: %d, frame_time: %d, now_us: %" PRIu64, + ps->last_msc, render_time, frame_time, now_us); + return 0; + } + log_trace("Delay: %lf s, last_msc: %" PRIu64 ", render_time: %d, frame_time: %d, " + "now_us: %" PRIu64 ", next_msc: %" PRIu64, + (double)(deadline - now_us - slack) / 1000000.0, ps->last_msc, + render_time, frame_time, now_us, next_msc); + return (double)(deadline - now_us - slack) / 1000000.0; +} + +/// Update render stats. Return false if a frame is still being rendered. +static bool update_render_stats(session_t *ps) { + if (!ps->first_frame && ps->redirected && ps->backend_data->ops->last_render_time) { + struct timespec render_time; + if (ps->backend_data->ops->last_render_time(ps->backend_data, &render_time)) { + int render_time_us = + (int)(render_time.tv_sec * 1000000 + render_time.tv_nsec / 1000); + log_trace("Last render call took: %d us, " + "rolling_max: %d us", + render_time_us, rolling_max_get_max(ps->render_stats)); + rolling_max_push(ps->render_stats, render_time_us); + return true; + } + return false; + } + return true; +} + void queue_redraw(session_t *ps) { + // Whether we have already rendered for the current frame. + // If frame pacing is not enabled, pretend this is false. + bool already_rendered = (ps->last_render > ps->last_msc) && ps->frame_pacing; + if (already_rendered) { + log_debug("Already rendered for the current frame, not queuing " + "redraw"); + } // If --benchmark is used, redraw is always queued - if (!ps->redraw_needed && !ps->o.benchmark) { - if (!ps->first_frame && ps->redirected && - ps->backend_data->ops->last_render_time) { - struct timespec render_time; - if (ps->backend_data->ops->last_render_time(ps->backend_data, - &render_time)) { - int render_time_us = (int)(render_time.tv_sec * 1000000 + - render_time.tv_nsec / 1000); - log_info( - "Last render call took: %d us, rolling_max: %d us", - render_time_us, rolling_max_get_max(ps->render_stats)); - rolling_max_push(ps->render_stats, render_time_us); + if (!ps->redraw_needed && !ps->o.benchmark && !already_rendered) { + double delay = 0; + if (ps->frame_pacing) { + if (!update_render_stats(ps)) { + // A frame is still being rendered. This means we missed + // previous vblank. We shouldn't queue a new frame until + // the next vblank. + log_debug("A frame is still being rendered, not queueing " + "redraw"); + ps->redraw_needed = true; + return; + } + // Our loop can be blocked by frame present, which cause ev_now to + // drift away from the real time. We need to correct it. + delay = next_frame_offset(ps) + ev_now(ps->loop) - ev_time(); + if (delay < 0) { + delay = 0; } } - ev_idle_start(ps->loop, &ps->draw_idle); + // Not doing frame pacing, just redraw immediately + ev_timer_set(&ps->draw_timer, delay, 0); + assert(!ev_is_active(&ps->draw_timer)); + ev_timer_start(ps->loop, &ps->draw_timer); } ps->redraw_needed = true; } @@ -1304,7 +1368,16 @@ static bool redirect_start(session_t *ps) { pixman_region32_init(&ps->damage_ring[i]); } - if (ps->present_exists) { + ps->frame_pacing = true; + if (ps->o.legacy_backends || ps->o.benchmark || + !ps->backend_data->ops->last_render_time) { + // Disable frame pacing if we are using a legacy backend or if we are in + // benchmark mode, or if the backend doesn't report render time + log_info("Disabling frame pacing."); + ps->frame_pacing = false; + } + + if (ps->present_exists && ps->frame_pacing) { ps->present_event_id = x_new_id(ps->c); auto select_input = xcb_present_select_input( ps->c, ps->present_event_id, session_get_target_window(ps), @@ -1316,10 +1389,17 @@ static bool redirect_start(session_t *ps) { ps->present_event = xcb_register_for_special_xge( ps->c, &xcb_present_id, ps->present_event_id, NULL); - // Initialize rendering and frame timing statistics. + // Initialize rendering and frame timing statistics, and frame pacing + // states. + ps->last_msc_instant = 0; ps->last_msc = 0; ps->last_render = 0; + ps->target_msc = 0; rolling_avg_reset(ps->frame_time); + rolling_max_reset(ps->render_stats); + } else if (ps->frame_pacing) { + log_error("Present extension is not supported, frame pacing disabled."); + ps->frame_pacing = false; } // Must call XSync() here @@ -1383,6 +1463,7 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) { return; } + if (cne->ust <= ps->last_msc) { return; } @@ -1391,13 +1472,46 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ cne->msc + 1, 0, 0); set_cant_fail_cookie(ps, cookie); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t now_usec = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000); + uint64_t drift; + if (cne->ust > now_usec) { + drift = cne->ust - now_usec; + } else { + drift = now_usec - cne->ust; + } + if (ps->last_msc != 0) { int frame_time = (int)(cne->ust - ps->last_msc); rolling_avg_push(ps->frame_time, frame_time); - log_trace("Frame time: %d us, rolling average: %lf us, msc: %" PRIu64, - frame_time, rolling_avg_get_avg(ps->frame_time), cne->ust); + log_trace("Frame time: %d us, rolling average: %lf us, msc: %" PRIu64 + ", offset: %" PRIu64, + frame_time, rolling_avg_get_avg(ps->frame_time), cne->ust, drift); } ps->last_msc = cne->ust; + if (drift > 1000000 && ps->frame_pacing) { + log_error("Temporal anomaly detected, frame pacing disabled. (Are we " + "running inside a time namespace?), %" PRIu64 " %" PRIu64, + now_usec, ps->last_msc); + ps->frame_pacing = false; + // We could have deferred a frame in queue_redraw() because of frame + // pacing. Unconditionally queue a frame for simplicity. + queue_redraw(ps); + } + if (ps->frame_pacing && ps->redraw_needed && !ev_is_active(&ps->draw_timer)) { + log_trace("Frame pacing: queueing redraw"); + // We deferred a frame in queue_redraw() because of frame pacing. Schedule + // it now. + if (!update_render_stats(ps)) { + log_warn("A frame is still being rendered, not queueing redraw."); + } else { + + ev_timer_set(&ps->draw_timer, + next_frame_offset(ps) + ev_now(ps->loop) - ev_time(), 0); + ev_timer_start(ps->loop, &ps->draw_timer); + } + } } static void handle_present_events(session_t *ps) { @@ -1613,6 +1727,12 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { } log_trace("Render end"); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t now_us = + (uint64_t)now.tv_sec * 1000000ULL + (uint64_t)now.tv_nsec / 1000; + ps->last_render = now_us; + ps->first_frame = false; paint++; if (ps->o.benchmark && paint >= ps->o.benchmark) { @@ -1627,18 +1747,21 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { // TODO(yshui) Investigate how big the X critical section needs to be. There are // suggestions that rendering should be in the critical section as well. + // Queue redraw if animation is running. This should be picked up by next present + // event. ps->redraw_needed = animation; } -static void draw_callback(EV_P_ ev_idle *w, int revents) { - session_t *ps = session_ptr(w, draw_idle); +static void draw_callback(EV_P_ ev_timer *w, int revents) { + session_t *ps = session_ptr(w, draw_timer); draw_callback_impl(EV_A_ ps, revents); + ev_timer_stop(EV_A_ w); - // Don't do painting non-stop unless we are in benchmark mode, or if - // draw_callback_impl thinks we should continue painting. - if (!ps->o.benchmark && !ps->redraw_needed) { - ev_idle_stop(EV_A_ & ps->draw_idle); + // Immediately start next frame if we are in benchmark mode. + if (ps->o.benchmark) { + ev_timer_set(w, 0, 0); + ev_timer_start(EV_A_ w); } } @@ -2252,7 +2375,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ); ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); - ev_idle_init(&ps->draw_idle, draw_callback); + ev_init(&ps->draw_timer, draw_callback); ev_init(&ps->fade_timer, fade_timer_callback); @@ -2550,7 +2673,7 @@ static void session_destroy(session_t *ps) { ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); ev_timer_stop(ps->loop, &ps->dpms_check_timer); - ev_idle_stop(ps->loop, &ps->draw_idle); + ev_timer_stop(ps->loop, &ps->draw_timer); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); ev_signal_stop(ps->loop, &ps->int_signal); @@ -2562,9 +2685,10 @@ static void session_destroy(session_t *ps) { * @param ps current session */ static void session_run(session_t *ps) { - // In benchmark mode, we want draw_idle handler to always be active + // In benchmark mode, we want draw_timer handler to always be active if (ps->o.benchmark) { - ev_idle_start(ps->loop, &ps->draw_idle); + ev_timer_set(&ps->draw_timer, 0, 0); + ev_timer_start(ps->loop, &ps->draw_timer); } else { // Let's draw our first frame! queue_redraw(ps); From 47ffaf0a38e041c7df9084149ccae1bd812bf74e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 15 Dec 2022 09:53:31 +0000 Subject: [PATCH 063/177] core: workaround X present event quirk When the screen turns off, X sometimes sends present complete notify for the same frame multiple times, or even events with invalid msc/ust number. This will cause us to ignore it and not send a subsequent NotifyMsc request, causing the complete notify to stop. Now we send NotifyMsc regardless to keep the events going. Also detect when the complete notifies skip frames, divide the interval by frame count to estimate frame time in that case. Upstream bug report: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 Signed-off-by: Yuxuan Shui --- src/common.h | 4 +++- src/picom.c | 38 +++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/common.h b/src/common.h index e6bbff0624..c3d5f82986 100644 --- a/src/common.h +++ b/src/common.h @@ -242,7 +242,9 @@ typedef struct session { /// Event context for X Present extension. uint32_t present_event_id; xcb_special_event_t *present_event; - /// Last MSC event, in useconds. + /// When last MSC event happened, in useconds. + uint64_t last_msc_instant; + /// The last MSC number uint64_t last_msc; /// When did we render our last frame. uint64_t last_render; diff --git a/src/picom.c b/src/picom.c index b5867413e9..d670fadfe6 100644 --- a/src/picom.c +++ b/src/picom.c @@ -166,7 +166,7 @@ double next_frame_offset(session_t *ps) { return 0; } int frame_time = (int)rolling_avg_get_avg(ps->frame_time); - auto next_msc = ps->last_msc + (uint64_t)frame_time; + auto next_msc = ps->last_msc_instant + (uint64_t)frame_time; auto deadline = next_msc - (uint64_t)render_time; const uint64_t slack = 1000; @@ -177,12 +177,12 @@ double next_frame_offset(session_t *ps) { // We are already late, render immediately. log_trace("Already late, rendering immediately, last_msc: %" PRIu64 ", render_time: %d, frame_time: %d, now_us: %" PRIu64, - ps->last_msc, render_time, frame_time, now_us); + ps->last_msc_instant, render_time, frame_time, now_us); return 0; } log_trace("Delay: %lf s, last_msc: %" PRIu64 ", render_time: %d, frame_time: %d, " "now_us: %" PRIu64 ", next_msc: %" PRIu64, - (double)(deadline - now_us - slack) / 1000000.0, ps->last_msc, + (double)(deadline - now_us - slack) / 1000000.0, ps->last_msc_instant, render_time, frame_time, now_us, next_msc); return (double)(deadline - now_us - slack) / 1000000.0; } @@ -208,7 +208,7 @@ static bool update_render_stats(session_t *ps) { void queue_redraw(session_t *ps) { // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. - bool already_rendered = (ps->last_render > ps->last_msc) && ps->frame_pacing; + bool already_rendered = (ps->last_render > ps->last_msc_instant) && ps->frame_pacing; if (already_rendered) { log_debug("Already rendered for the current frame, not queuing " "redraw"); @@ -1464,14 +1464,18 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ return; } - if (cne->ust <= ps->last_msc) { - return; - } - auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), 0, cne->msc + 1, 0, 0); set_cant_fail_cookie(ps, cookie); + if (cne->msc <= ps->last_msc || cne->ust == 0) { + // X sometimes sends duplicate/bogus MSC events, ignore them + // + // See: + // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 + return; + } + struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); uint64_t now_usec = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000); @@ -1482,18 +1486,21 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ drift = now_usec - cne->ust; } - if (ps->last_msc != 0) { - int frame_time = (int)(cne->ust - ps->last_msc); + if (ps->last_msc_instant != 0) { + auto frame_count = cne->msc - ps->last_msc; + int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); rolling_avg_push(ps->frame_time, frame_time); - log_trace("Frame time: %d us, rolling average: %lf us, msc: %" PRIu64 - ", offset: %" PRIu64, - frame_time, rolling_avg_get_avg(ps->frame_time), cne->ust, drift); + log_trace("Frame count %lu, frame time: %d us, rolling average: %lf us, " + "msc: %" PRIu64 ", offset: %" PRIu64, + frame_count, frame_time, rolling_avg_get_avg(ps->frame_time), + cne->ust, drift); } - ps->last_msc = cne->ust; + ps->last_msc_instant = cne->ust; + ps->last_msc = cne->msc; if (drift > 1000000 && ps->frame_pacing) { log_error("Temporal anomaly detected, frame pacing disabled. (Are we " "running inside a time namespace?), %" PRIu64 " %" PRIu64, - now_usec, ps->last_msc); + now_usec, ps->last_msc_instant); ps->frame_pacing = false; // We could have deferred a frame in queue_redraw() because of frame // pacing. Unconditionally queue a frame for simplicity. @@ -1516,6 +1523,7 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ static void handle_present_events(session_t *ps) { if (!ps->present_event) { + // Screen not redirected return; } xcb_present_generic_event_t *ev; From a4fae2b60a576d0c50b4134ff0c65d47a5e9fa30 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 15 Dec 2022 17:24:14 +0000 Subject: [PATCH 064/177] core: better frame pacing function Details explained in the comments on schedule_render(). Signed-off-by: Yuxuan Shui --- src/common.h | 3 + src/picom.c | 173 ++++++++++++++++++++++++++++----------------------- 2 files changed, 97 insertions(+), 79 deletions(-) diff --git a/src/common.h b/src/common.h index c3d5f82986..b76863df15 100644 --- a/src/common.h +++ b/src/common.h @@ -246,6 +246,9 @@ typedef struct session { uint64_t last_msc_instant; /// The last MSC number uint64_t last_msc; + /// When the currently rendered frame will be displayed. + /// 0 means there is no pending frame. + uint64_t target_msc; /// When did we render our last frame. uint64_t last_render; /// Whether we can perform frame pacing. diff --git a/src/picom.c b/src/picom.c index d670fadfe6..25d22128bf 100644 --- a/src/picom.c +++ b/src/picom.c @@ -157,86 +157,111 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t } /// How many seconds into the future should we start rendering the next frame. -double next_frame_offset(session_t *ps) { - int render_time = rolling_max_get_max(ps->render_stats); - if (render_time < 0) { - // We don't have render time estimates, maybe there's no frame rendered - // yet, or the backend doesn't support render timing information, - // queue render immediately. - return 0; +/// +/// Renders are scheduled like this: +/// +/// 1. queue_redraw() registers the intention to render. redraw_needed is set to true to +/// indicate what is on screen needs to be updated. +/// 2. then, we need to figure out the best time to start rendering. first, we need to +/// know when the next frame will be displayed on screen. we have this information from +/// the Present extension: we know when was the last frame displayed, and we know the +/// refresh rate. so we can calculate the next frame's display time. if our render time +/// estimation shows we could miss that target, we push the target back one frame. +/// 3. if there is already render completed for that target frame, or there is a render +/// currently underway, we don't do anything, and wait for the next Present Complete +/// Notify event to try to schedule again. +/// 4. otherwise, we schedule a render for that target frame. we use past statistics about +/// how long our renders took to figure out when to start rendering. we start rendering +/// at the latest point of time possible to still hit the target frame. +void schedule_render(session_t *ps) { + double delay_s = 0; + if (!ps->frame_pacing || !ps->redirected) { + // Not doing frame pacing, schedule a render immediately, if not already + // scheduled. + // If not redirected, we schedule immediately to have a chance to + // redirect. We won't have frame or render timing information anyway. + if (!ev_is_active(&ps->draw_timer)) { + // We don't know the msc, so we set it to 1, because 0 is a + // special value + ps->target_msc = 1; + goto schedule; + } + return; + } + struct timespec render_time; + bool completed = + ps->backend_data->ops->last_render_time(ps->backend_data, &render_time); + if (!completed || ev_is_active(&ps->draw_timer)) { + // There is already a render underway (either just scheduled, or is + // rendered but awaiting completion), don't schedule another one. + if (ps->target_msc <= ps->last_msc) { + log_debug("Target frame %ld is in the past, but we are still " + "rendering", + ps->target_msc); + // We missed our target, push it back one frame + ps->target_msc = ps->last_msc + 1; + } + return; + } + if (ps->target_msc > ps->last_msc) { + // Render for the target frame is completed, and has yet to be displayed. + // Don't schedule another render. + return; } - int frame_time = (int)rolling_avg_get_avg(ps->frame_time); - auto next_msc = ps->last_msc_instant + (uint64_t)frame_time; - auto deadline = next_msc - (uint64_t)render_time; - const uint64_t slack = 1000; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; - if (now_us + slack >= deadline) { - // We are already late, render immediately. - log_trace("Already late, rendering immediately, last_msc: %" PRIu64 - ", render_time: %d, frame_time: %d, now_us: %" PRIu64, - ps->last_msc_instant, render_time, frame_time, now_us); - return 0; - } - log_trace("Delay: %lf s, last_msc: %" PRIu64 ", render_time: %d, frame_time: %d, " - "now_us: %" PRIu64 ", next_msc: %" PRIu64, - (double)(deadline - now_us - slack) / 1000000.0, ps->last_msc_instant, - render_time, frame_time, now_us, next_msc); - return (double)(deadline - now_us - slack) / 1000000.0; -} -/// Update render stats. Return false if a frame is still being rendered. -static bool update_render_stats(session_t *ps) { - if (!ps->first_frame && ps->redirected && ps->backend_data->ops->last_render_time) { - struct timespec render_time; - if (ps->backend_data->ops->last_render_time(ps->backend_data, &render_time)) { - int render_time_us = - (int)(render_time.tv_sec * 1000000 + render_time.tv_nsec / 1000); - log_trace("Last render call took: %d us, " - "rolling_max: %d us", - render_time_us, rolling_max_get_max(ps->render_stats)); - rolling_max_push(ps->render_stats, render_time_us); - return true; - } - return false; + int render_time_us = + (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); + if (ps->target_msc == ps->last_msc) { + // The frame has just been displayed, record its render time; + log_trace("Last render call took: %d us, rolling_max: %d us", + render_time_us, rolling_max_get_max(ps->render_stats)); + rolling_max_push(ps->render_stats, render_time_us); + ps->target_msc = 0; } - return true; + + // TODO(yshui): why 1500? + const int SLACK = 1500; + int render_time_estimate = rolling_max_get_max(ps->render_stats); + if (render_time_estimate < 0) { + // We don't have render time estimates, maybe there's no frame rendered + // yet, or the backend doesn't support render timing information, + // schedule render immediately. + ps->target_msc = ps->last_msc + 1; + goto schedule; + } + render_time_estimate += SLACK; + + auto frame_time = (uint64_t)rolling_avg_get_avg(ps->frame_time); + auto minimal_target_us = now_us + (uint64_t)render_time_estimate; + auto frame_delay = (uint64_t)ceil( + (double)(minimal_target_us - ps->last_msc_instant) / (double)frame_time); + auto deadline = ps->last_msc_instant + frame_delay * frame_time; + + ps->target_msc = ps->last_msc + frame_delay; + auto delay = deadline - (uint64_t)render_time_estimate - now_us; + delay_s = (double)delay / 1000000.0; + + log_trace("Delay: %lu us, last_msc: %" PRIu64 ", render_time: %d, frame_time: " + "%" PRIu64 ", now_us: %" PRIu64 ", next_msc: %" PRIu64, + delay, ps->last_msc_instant, render_time_estimate, frame_time, now_us, + deadline); + +schedule: + 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) { // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. - bool already_rendered = (ps->last_render > ps->last_msc_instant) && ps->frame_pacing; - if (already_rendered) { - log_debug("Already rendered for the current frame, not queuing " - "redraw"); - } // If --benchmark is used, redraw is always queued - if (!ps->redraw_needed && !ps->o.benchmark && !already_rendered) { - double delay = 0; - if (ps->frame_pacing) { - if (!update_render_stats(ps)) { - // A frame is still being rendered. This means we missed - // previous vblank. We shouldn't queue a new frame until - // the next vblank. - log_debug("A frame is still being rendered, not queueing " - "redraw"); - ps->redraw_needed = true; - return; - } - // Our loop can be blocked by frame present, which cause ev_now to - // drift away from the real time. We need to correct it. - delay = next_frame_offset(ps) + ev_now(ps->loop) - ev_time(); - if (delay < 0) { - delay = 0; - } - } - // Not doing frame pacing, just redraw immediately - ev_timer_set(&ps->draw_timer, delay, 0); - assert(!ev_is_active(&ps->draw_timer)); - ev_timer_start(ps->loop, &ps->draw_timer); + if (!ps->redraw_needed && !ps->o.benchmark) { + schedule_render(ps); } ps->redraw_needed = true; } @@ -1506,18 +1531,8 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ // pacing. Unconditionally queue a frame for simplicity. queue_redraw(ps); } - if (ps->frame_pacing && ps->redraw_needed && !ev_is_active(&ps->draw_timer)) { - log_trace("Frame pacing: queueing redraw"); - // We deferred a frame in queue_redraw() because of frame pacing. Schedule - // it now. - if (!update_render_stats(ps)) { - log_warn("A frame is still being rendered, not queueing redraw."); - } else { - - ev_timer_set(&ps->draw_timer, - next_frame_offset(ps) + ev_now(ps->loop) - ev_time(), 0); - ev_timer_start(ps->loop, &ps->draw_timer); - } + if (ps->frame_pacing && ps->redraw_needed) { + schedule_render(ps); } } From 480fd24da6ebca65e72d32ae6bbdd6e238282434 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 16 Dec 2022 08:52:20 +0000 Subject: [PATCH 065/177] core: stop sending NotifyMsc if frame pacing is disabled Signed-off-by: Yuxuan Shui --- src/picom.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index 25d22128bf..0727847219 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1489,9 +1489,11 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ return; } - auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), 0, - cne->msc + 1, 0, 0); - set_cant_fail_cookie(ps, cookie); + if (ps->frame_pacing) { + auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), + 0, cne->msc + 1, 0, 0); + set_cant_fail_cookie(ps, cookie); + } if (cne->msc <= ps->last_msc || cne->ust == 0) { // X sometimes sends duplicate/bogus MSC events, ignore them From 1899ef0d494a5309cf2dacbd6148186e6fb9c1d1 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 16 Dec 2022 08:53:08 +0000 Subject: [PATCH 066/177] core: only check present timer alignment in the first frame Timer can sometimes drift randomly, like when the system is suspended. we don't want to disable frame pacing for that Signed-off-by: Yuxuan Shui --- src/picom.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/picom.c b/src/picom.c index 0727847219..65883beeec 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1518,22 +1518,21 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); rolling_avg_push(ps->frame_time, frame_time); log_trace("Frame count %lu, frame time: %d us, rolling average: %lf us, " - "msc: %" PRIu64 ", offset: %" PRIu64, + "msc: %" PRIu64 ", offset: %d us", frame_count, frame_time, rolling_avg_get_avg(ps->frame_time), - cne->ust, drift); - } - ps->last_msc_instant = cne->ust; - ps->last_msc = cne->msc; - if (drift > 1000000 && ps->frame_pacing) { + cne->ust, (int)drift); + } else if (drift > 1000000 && ps->frame_pacing) { + // This is the first MSC event we receive, let's check if the timestamps + // align with the monotonic clock. If not, disable frame pacing because we + // can't schedule frames reliably. log_error("Temporal anomaly detected, frame pacing disabled. (Are we " "running inside a time namespace?), %" PRIu64 " %" PRIu64, now_usec, ps->last_msc_instant); ps->frame_pacing = false; - // We could have deferred a frame in queue_redraw() because of frame - // pacing. Unconditionally queue a frame for simplicity. - queue_redraw(ps); } - if (ps->frame_pacing && ps->redraw_needed) { + ps->last_msc_instant = cne->ust; + ps->last_msc = cne->msc; + if (ps->redraw_needed) { schedule_render(ps); } } From edeb17ad4637feec72aaba506ad7fc4a5cac811e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 17 Dec 2022 19:27:40 +0000 Subject: [PATCH 067/177] core: don't fully trust msc from present complete notify Sometimes X sends bogus complete notifies with invalid msc number. Instead use a saved msc number to get the next complete notify. Signed-off-by: Yuxuan Shui --- src/picom.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/picom.c b/src/picom.c index 65883beeec..3e0fd786df 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1489,17 +1489,23 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ return; } + bool event_is_invalid = false; if (ps->frame_pacing) { + auto next_msc = cne->msc + 1; + if (cne->msc <= ps->last_msc || cne->ust == 0) { + // X sometimes sends duplicate/bogus MSC events, don't + // use the msc value. Also ignore these events. + // + // See: + // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 + next_msc = ps->last_msc + 1; + event_is_invalid = true; + } auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), - 0, cne->msc + 1, 0, 0); + 0, next_msc, 0, 0); set_cant_fail_cookie(ps, cookie); } - - if (cne->msc <= ps->last_msc || cne->ust == 0) { - // X sometimes sends duplicate/bogus MSC events, ignore them - // - // See: - // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 + if (event_is_invalid) { return; } From fcb9dc8cfd41d3f5ae3463ab4625ffd03daa25e4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 7 Mar 2023 17:03:26 +0000 Subject: [PATCH 068/177] core: make sure unredirection happens when screen is off So when the screen is off, we calls queue_redraw, hoping draw_callback will be called and unredirects the screen. However, queue_redraw doesn't queue another redraw when one is already queued. Redraws can be queued in two ways: one is timer based, which is fine, because it will be triggered no matter what; the other is frame based, which is triggered by Present events. When the screen is off, X server, depends on the driver, could send abnormal Present events, which we ignore. But that also means queued frame based redraw will stop being triggered. Those two factors combined means sometimes unredirection does not happen when the screen is off. Which means we aren't going to free the GL context, which are still receiving Present events, but can't handle them, because we are not rendering anything with GL. In the end all these causes memory usage to balloon, until the screen is turned on and we start rendering again. And all these is not caught by leak checkers because this technically is not a leak, as everything is eventually freed. Signed-off-by: Yuxuan Shui --- src/picom.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index 3e0fd786df..e97f0e6fae 100644 --- a/src/picom.c +++ b/src/picom.c @@ -133,6 +133,7 @@ void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { } auto now_screen_is_off = dpms_screen_is_off(r); if (ps->screen_is_off != now_screen_is_off) { + log_debug("Screen is now %s", now_screen_is_off ? "off" : "on"); ps->screen_is_off = now_screen_is_off; queue_redraw(ps); } @@ -145,14 +146,17 @@ void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { * XXX move to win.c */ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) { - if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) + if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) { return NULL; + } auto w = find_managed_win(ps, wid); - if (!w) + if (!w) { w = find_toplevel(ps, wid); - if (!w) + } + if (!w) { w = find_managed_window_or_parent(ps, wid); + } return w; } @@ -244,6 +248,11 @@ void schedule_render(session_t *ps) { ps->target_msc = ps->last_msc + frame_delay; auto delay = deadline - (uint64_t)render_time_estimate - now_us; delay_s = (double)delay / 1000000.0; + if (delay_s > 1) { + log_warn("Delay too long: %f s, render_time: %d us, frame_time: %" PRIu64 + " us, now_us: %" PRIu64 " us, next_msc: %" PRIu64 " us", + delay_s, render_time_estimate, frame_time, now_us, deadline); + } log_trace("Delay: %lu us, last_msc: %" PRIu64 ", render_time: %d, frame_time: " "%" PRIu64 ", now_us: %" PRIu64 ", next_msc: %" PRIu64, @@ -257,6 +266,18 @@ void schedule_render(session_t *ps) { } void queue_redraw(session_t *ps) { + if (ps->screen_is_off) { + // The screen is off, if there is a draw queued for the next frame (i.e. + // ps->redraw_needed == true), it won't be triggered until the screen is + // on again, because the abnormal Present events we will receive from the + // X server when the screen is off. Yet we need the draw_callback to be + // called as soon as possible so the screen can be unredirected. + // So here we unconditionally start the draw timer. + ev_timer_stop(ps->loop, &ps->draw_timer); + ev_timer_set(&ps->draw_timer, 0, 0); + ev_timer_start(ps->loop, &ps->draw_timer); + return; + } // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. // If --benchmark is used, redraw is always queued From 336cb0917adbfabe264f431de20f0cffa45fdb18 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Apr 2023 04:06:56 +0100 Subject: [PATCH 069/177] core: check we have both frame time and render time before pacing We only checked render time. If we don't have frame time estimates, we would divide by zero and end up with wild scheduling delays. Signed-off-by: Yuxuan Shui --- src/picom.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/picom.c b/src/picom.c index e97f0e6fae..8f217b1464 100644 --- a/src/picom.c +++ b/src/picom.c @@ -230,16 +230,16 @@ void schedule_render(session_t *ps) { // TODO(yshui): why 1500? const int SLACK = 1500; int render_time_estimate = rolling_max_get_max(ps->render_stats); - if (render_time_estimate < 0) { - // We don't have render time estimates, maybe there's no frame rendered - // yet, or the backend doesn't support render timing information, - // schedule render immediately. + auto frame_time = (uint64_t)rolling_avg_get_avg(ps->frame_time); + if (render_time_estimate < 0 || frame_time == 0) { + // We don't have render time, and/or frame time estimates, maybe there's + // no frame rendered yet, or the backend doesn't support render timing + // information, schedule render immediately. ps->target_msc = ps->last_msc + 1; goto schedule; } render_time_estimate += SLACK; - auto frame_time = (uint64_t)rolling_avg_get_avg(ps->frame_time); auto minimal_target_us = now_us + (uint64_t)render_time_estimate; auto frame_delay = (uint64_t)ceil( (double)(minimal_target_us - ps->last_msc_instant) / (double)frame_time); From 8e1f3c92f54ee10793cf06d260cfd57abf4c28db Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 10 Jun 2023 14:08:49 +0100 Subject: [PATCH 070/177] core: factor out code for estimating the rendering time budget Add a render_statistics type to encapsulate all the statistics done on rendering times. And use that to estimate the time budget for rendering and frame pacing. Tweak the rolling window utilities a bit so we can reuse one rolling window for multiple statistics. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 14 +++ src/common.h | 12 ++- src/meson.build | 2 +- src/picom.c | 115 ++++++++++++--------- src/statistics.c | 100 ++++++++++++++++++ src/statistics.h | 33 ++++++ src/utils.c | 233 ++++++++++++++++++++++++------------------ src/utils.h | 94 +++++++++++++++-- 8 files changed, 441 insertions(+), 162 deletions(-) create mode 100644 src/statistics.c create mode 100644 src/statistics.h diff --git a/src/backend/backend.c b/src/backend/backend.c index 3543ee127e..bacf29d28b 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -176,6 +176,20 @@ void paint_all_new(session_t *ps, struct managed_win *t) { region_t reg_shadow_clip; pixman_region32_init(®_shadow_clip); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + auto now_us = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000); + if (ps->next_render > 0) { + log_trace("Render schedule deviation: %ld us (%s) %ld %ld", + labs((int64_t)now_us - (int64_t)ps->next_render), + now_us < ps->next_render ? "early" : "late", now_us, + ps->next_render); + ps->last_schedule_delay = 0; + if (now_us > ps->next_render) { + ps->last_schedule_delay = now_us - ps->next_render; + } + } + if (ps->backend_data->ops->prepare) { ps->backend_data->ops->prepare(ps->backend_data, ®_paint); } diff --git a/src/common.h b/src/common.h index b76863df15..65d0e7b65f 100644 --- a/src/common.h +++ b/src/common.h @@ -58,6 +58,7 @@ #include "list.h" #include "region.h" #include "render.h" +#include "statistics.h" #include "types.h" #include "utils.h" #include "win_defs.h" @@ -249,13 +250,16 @@ typedef struct session { /// When the currently rendered frame will be displayed. /// 0 means there is no pending frame. uint64_t target_msc; - /// When did we render our last frame. - uint64_t last_render; + /// The delay between when the last frame was scheduled to be rendered, and when + /// the render actually started. + uint64_t last_schedule_delay; + /// When do we want our next frame to start rendering. + uint64_t next_render; /// Whether we can perform frame pacing. bool frame_pacing; - struct rolling_avg *frame_time; - struct rolling_max *render_stats; + /// Render statistics + struct render_statistics render_stats; // === Operation related === /// Flags related to the root window diff --git a/src/meson.build b/src/meson.build index 0cd87c896d..074683b2a3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,7 +9,7 @@ base_deps = [ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', - 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c') ] + 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c') ] picom_inc = include_directories('.') cflags = [] diff --git a/src/picom.c b/src/picom.c index 8f217b1464..8bb22d7d0e 100644 --- a/src/picom.c +++ b/src/picom.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,6 @@ #endif #include "backend/backend.h" #include "c2.h" -#include "config.h" #include "diagnostic.h" #include "log.h" #include "region.h" @@ -60,6 +60,7 @@ #include "file_watch.h" #include "list.h" #include "options.h" +#include "statistics.h" #include "uthash_extra.h" /// Get session_t pointer from a pointer to a member of session_t @@ -177,8 +178,17 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t /// 4. otherwise, we schedule a render for that target frame. we use past statistics about /// how long our renders took to figure out when to start rendering. we start rendering /// at the latest point of time possible to still hit the target frame. -void schedule_render(session_t *ps) { +/// +/// 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. + +void schedule_render(session_t *ps, bool triggered_by_vblank) { double delay_s = 0; + ps->next_render = 0; if (!ps->frame_pacing || !ps->redirected) { // Not doing frame pacing, schedule a render immediately, if not already // scheduled. @@ -205,59 +215,77 @@ void schedule_render(session_t *ps) { // We missed our target, push it back one frame ps->target_msc = ps->last_msc + 1; } + log_trace("Still rendering for target frame %ld, not scheduling another " + "render", + ps->target_msc); return; } if (ps->target_msc > ps->last_msc) { - // Render for the target frame is completed, and has yet to be displayed. + // Render for the target frame is completed, but is yet to be displayed. // Don't schedule another render. + log_trace("Target frame %ld is in the future, and we have already " + "rendered, last msc: %d", + ps->target_msc, (int)ps->last_msc); return; } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; + if (triggered_by_vblank) { + log_trace("vblank schedule delay: %ld us", now_us - ps->last_msc_instant); + } int render_time_us = (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); if (ps->target_msc == ps->last_msc) { // The frame has just been displayed, record its render time; - log_trace("Last render call took: %d us, rolling_max: %d us", - render_time_us, rolling_max_get_max(ps->render_stats)); - rolling_max_push(ps->render_stats, render_time_us); + log_trace("Last render call took: %d (gpu) + %d (cpu) us, " + "last_msc: %" PRIu64, + render_time_us, (int)ps->last_schedule_delay, ps->last_msc); + render_statistics_add_render_time_sample( + &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); ps->target_msc = 0; + ps->last_schedule_delay = 0; } - // TODO(yshui): why 1500? - const int SLACK = 1500; - int render_time_estimate = rolling_max_get_max(ps->render_stats); - auto frame_time = (uint64_t)rolling_avg_get_avg(ps->frame_time); - if (render_time_estimate < 0 || frame_time == 0) { - // We don't have render time, and/or frame time estimates, maybe there's + unsigned int divisor = 0; + auto render_budget = render_statistics_get_budget(&ps->render_stats, &divisor); + 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. ps->target_msc = ps->last_msc + 1; goto schedule; } - render_time_estimate += SLACK; - auto minimal_target_us = now_us + (uint64_t)render_time_estimate; - auto frame_delay = (uint64_t)ceil( - (double)(minimal_target_us - ps->last_msc_instant) / (double)frame_time); - auto deadline = ps->last_msc_instant + frame_delay * frame_time; + const auto deadline = ps->last_msc_instant + (unsigned long)divisor * frame_time; + unsigned int available = 0; + if (deadline > now_us) { + available = (unsigned int)(deadline - now_us); + } - ps->target_msc = ps->last_msc + frame_delay; - auto delay = deadline - (uint64_t)render_time_estimate - now_us; - delay_s = (double)delay / 1000000.0; + ps->target_msc = ps->last_msc + divisor; + if (available > render_budget) { + delay_s = (double)(available - render_budget) / 1000000.0; + ps->next_render = deadline - render_budget; + } else { + delay_s = 0; + ps->next_render = now_us; + } if (delay_s > 1) { - log_warn("Delay too long: %f s, render_time: %d us, frame_time: %" PRIu64 - " us, now_us: %" PRIu64 " us, next_msc: %" PRIu64 " us", - delay_s, render_time_estimate, frame_time, now_us, deadline); + 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_trace("Delay: %lu us, last_msc: %" PRIu64 ", render_time: %d, frame_time: " - "%" PRIu64 ", now_us: %" PRIu64 ", next_msc: %" PRIu64, - delay, ps->last_msc_instant, render_time_estimate, frame_time, now_us, - deadline); + log_trace("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, frame_time: " + "%" PRIu32 ", now_us: %" PRIu64 ", next_msc: %" PRIu64 ", " + "target_msc: %" PRIu64 ", divisor: %d", + delay_s, ps->last_msc_instant, render_budget, frame_time, now_us, + deadline, ps->target_msc, divisor); schedule: assert(!ev_is_active(&ps->draw_timer)); @@ -282,7 +310,7 @@ void queue_redraw(session_t *ps) { // If frame pacing is not enabled, pretend this is false. // If --benchmark is used, redraw is always queued if (!ps->redraw_needed && !ps->o.benchmark) { - schedule_render(ps); + schedule_render(ps, false); } ps->redraw_needed = true; } @@ -758,7 +786,6 @@ static void configure_root(session_t *ps) { } force_repaint(ps); } - return; } static void handle_root_flags(session_t *ps) { @@ -1439,10 +1466,9 @@ static bool redirect_start(session_t *ps) { // states. ps->last_msc_instant = 0; ps->last_msc = 0; - ps->last_render = 0; + ps->last_schedule_delay = 0; ps->target_msc = 0; - rolling_avg_reset(ps->frame_time); - rolling_max_reset(ps->render_stats); + render_statistics_reset(&ps->render_stats); } else if (ps->frame_pacing) { log_error("Present extension is not supported, frame pacing disabled."); ps->frame_pacing = false; @@ -1543,11 +1569,12 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ if (ps->last_msc_instant != 0) { auto frame_count = cne->msc - ps->last_msc; int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); - rolling_avg_push(ps->frame_time, frame_time); - log_trace("Frame count %lu, frame time: %d us, rolling average: %lf us, " + render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time); + log_trace("Frame count %lu, frame time: %d us, rolling average: %u us, " "msc: %" PRIu64 ", offset: %d us", - frame_count, frame_time, rolling_avg_get_avg(ps->frame_time), - cne->ust, (int)drift); + frame_count, frame_time, + render_statistics_get_vblank_time(&ps->render_stats), cne->ust, + (int)drift); } else if (drift > 1000000 && ps->frame_pacing) { // This is the first MSC event we receive, let's check if the timestamps // align with the monotonic clock. If not, disable frame pacing because we @@ -1560,7 +1587,7 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ ps->last_msc_instant = cne->ust; ps->last_msc = cne->msc; if (ps->redraw_needed) { - schedule_render(ps); + schedule_render(ps, true); } } @@ -1651,6 +1678,8 @@ static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents at } static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + // TODO(yshui): do we still need the fade timer? we queue redraw automatically in + // draw_callback_impl if animation is running. session_t *ps = session_ptr(w, fade_timer); queue_redraw(ps); } @@ -1778,12 +1807,6 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { } log_trace("Render end"); - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - uint64_t now_us = - (uint64_t)now.tv_sec * 1000000ULL + (uint64_t)now.tv_nsec / 1000; - ps->last_render = now_us; - ps->first_frame = false; paint++; if (ps->o.benchmark && paint >= ps->o.benchmark) { @@ -2004,8 +2027,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, pixman_region32_init(&ps->screen_reg); // TODO(yshui) investigate what's the best window size - ps->render_stats = rolling_max_new(128); - ps->frame_time = rolling_avg_new(16); + render_statistics_init(&ps->render_stats, 128); ps->pending_reply_tail = &ps->pending_reply_head; @@ -2642,8 +2664,7 @@ static void session_destroy(session_t *ps) { free(ps->o.glx_fshader_win_str); x_free_randr_info(ps); - rolling_avg_destroy(ps->frame_time); - rolling_max_destroy(ps->render_stats); + render_statistics_destroy(&ps->render_stats); // Release custom window shaders free(ps->o.window_shader_fg); diff --git a/src/statistics.c b/src/statistics.c new file mode 100644 index 0000000000..5e3d92491d --- /dev/null +++ b/src/statistics.c @@ -0,0 +1,100 @@ +//! Rendering statistics +//! +//! Tracks how long it takes to render a frame, for measuring performance, and for pacing +//! the frames. + +#include "statistics.h" +#include "log.h" +#include "utils.h" + +void render_statistics_init(struct render_statistics *rs, int window_size) { + *rs = (struct render_statistics){0}; + + rolling_window_init(&rs->render_times, window_size); + rolling_quantile_init_with_tolerance(&rs->render_time_quantile, window_size, + /* q */ 0.98, /* tolerance */ 0.01); +} + +void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us) { + auto sample_sd = sqrt(cumulative_mean_and_var_get_var(&rs->vblank_time_us)); + auto current_estimate = render_statistics_get_vblank_time(rs); + if (current_estimate != 0 && fabs((double)time_us - current_estimate) > sample_sd * 3) { + // Deviated from the mean by more than 3 sigma (p < 0.003) + log_debug("vblank time outlier: %d %f %f", time_us, rs->vblank_time_us.mean, + cumulative_mean_and_var_get_var(&rs->vblank_time_us)); + // An outlier sample, this could mean things like refresh rate changes, so + // we reset the statistics. This could also be benign, but we like to be + // cautious. + cumulative_mean_and_var_init(&rs->vblank_time_us); + } + + if (rs->vblank_time_us.mean != 0) { + auto nframes_in_10_seconds = + (unsigned int)(10. * 1000000. / rs->vblank_time_us.mean); + if (rs->vblank_time_us.n > 20 && rs->vblank_time_us.n > nframes_in_10_seconds) { + // We collected 10 seconds worth of samples, we assume the + // estimated refresh rate is stable. We will still reset the + // statistics if we get an outlier sample though, see above. + return; + } + } + cumulative_mean_and_var_update(&rs->vblank_time_us, time_us); +} + +void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us) { + int oldest; + if (rolling_window_push_back(&rs->render_times, time_us, &oldest)) { + rolling_quantile_pop_front(&rs->render_time_quantile, oldest); + } + + rolling_quantile_push_back(&rs->render_time_quantile, time_us); +} + +/// How much time budget we should give to the backend for rendering, in microseconds. +/// +/// A `divisor` is also returned, indicating the target framerate. The divisor is +/// the number of vblanks we should wait between each frame. A divisor of 1 means +/// full framerate, 2 means half framerate, etc. +unsigned int +render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor) { + if (rs->render_times.nelem < rs->render_times.window_size) { + // No valid render time estimates yet. Assume maximum budget. + *divisor = 1; + return UINT_MAX; + } + + // N-th percentile of render times, see render_statistics_init for N. + auto render_time_percentile = + rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times); + auto vblank_time_us = render_statistics_get_vblank_time(rs); + if (vblank_time_us == 0) { + // We don't have a good estimate of the vblank time yet, so we + // assume we can finish in one vblank. + *divisor = 1; + } else { + *divisor = + (unsigned int)(render_time_percentile / rs->vblank_time_us.mean + 1); + } + return (unsigned int)render_time_percentile; +} + +unsigned int render_statistics_get_vblank_time(struct render_statistics *rs) { + if (rs->vblank_time_us.n <= 20 || rs->vblank_time_us.mean < 100) { + // Not enough samples yet, or the vblank time is too short to be + // meaningful. Assume maximum budget. + return 0; + } + return (unsigned int)rs->vblank_time_us.mean; +} + +void render_statistics_reset(struct render_statistics *rs) { + rolling_window_reset(&rs->render_times); + rolling_quantile_reset(&rs->render_time_quantile); + rs->vblank_time_us = (struct cumulative_mean_and_var){0}; +} + +void render_statistics_destroy(struct render_statistics *rs) { + render_statistics_reset(rs); + rolling_window_destroy(&rs->render_times); + rolling_quantile_destroy(&rs->render_time_quantile); +} diff --git a/src/statistics.h b/src/statistics.h new file mode 100644 index 0000000000..67d02119af --- /dev/null +++ b/src/statistics.h @@ -0,0 +1,33 @@ +#pragma once + +#include "utils.h" + +#define NTIERS (3) + +struct render_statistics { + /// Rolling window of rendering times (in us) and the tiers they belong to. + /// We keep track of the tiers because the vblank time estimate can change over + /// time. + struct rolling_window render_times; + /// Estimate the 95-th percentile of rendering times + struct rolling_quantile render_time_quantile; + /// Time between each vblanks + struct cumulative_mean_and_var vblank_time_us; +}; + +void render_statistics_init(struct render_statistics *rs, int window_size); +void render_statistics_reset(struct render_statistics *rs); +void render_statistics_destroy(struct render_statistics *rs); + +void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us); +void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us); + +/// How much time budget we should give to the backend for rendering, in microseconds. +/// +/// A `divisor` is also returned, indicating the target framerate. The divisor is +/// the number of vblanks we should wait between each frame. A divisor of 1 means +/// full framerate, 2 means half framerate, etc. +unsigned int +render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor); + +unsigned int render_statistics_get_vblank_time(struct render_statistics *rs); diff --git a/src/utils.c b/src/utils.c index a1114a0491..d26afa331e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -48,7 +48,43 @@ int next_power_of_two(int n) { return n; } -/// Track the rolling maximum of a stream of integers. +void rolling_window_destroy(struct rolling_window *rw) { + free(rw->elem); + rw->elem = NULL; +} + +void rolling_window_reset(struct rolling_window *rw) { + rw->nelem = 0; + rw->elem_head = 0; +} + +void rolling_window_init(struct rolling_window *rw, int size) { + rw->elem = ccalloc(size, int); + rw->window_size = size; + rolling_window_reset(rw); +} + +int rolling_window_pop_front(struct rolling_window *rw) { + assert(rw->nelem > 0); + auto ret = rw->elem[rw->elem_head]; + rw->elem_head = (rw->elem_head + 1) % rw->window_size; + rw->nelem--; + return ret; +} + +bool rolling_window_push_back(struct rolling_window *rw, int val, int *front) { + bool full = rw->nelem == rw->window_size; + if (full) { + *front = rolling_window_pop_front(rw); + } + rw->elem[(rw->elem_head + rw->nelem) % rw->window_size] = val; + rw->nelem++; + return full; +} + +/// Track the maximum member of a FIFO queue of integers. Integers are pushed to the back +/// and popped from the front, the maximum of the current members in the queue is +/// tracked. struct rolling_max { /// A priority queue holding the indices of the maximum element candidates. /// The head of the queue is the index of the maximum element. @@ -59,32 +95,26 @@ struct rolling_max { /// it's called the "original" indices. int *p; int p_head, np; - - /// The elemets - int *elem; - int elem_head, nelem; - - int window_size; + /// The maximum number of in flight elements. + int capacity; }; void rolling_max_destroy(struct rolling_max *rm) { - free(rm->elem); free(rm->p); free(rm); } -struct rolling_max *rolling_max_new(int size) { +struct rolling_max *rolling_max_new(int capacity) { auto rm = ccalloc(1, struct rolling_max); if (!rm) { return NULL; } - rm->p = ccalloc(size, int); - rm->elem = ccalloc(size, int); - rm->window_size = size; - if (!rm->p || !rm->elem) { + rm->p = ccalloc(capacity, int); + if (!rm->p) { goto err; } + rm->capacity = capacity; return rm; @@ -96,33 +126,21 @@ struct rolling_max *rolling_max_new(int size) { void rolling_max_reset(struct rolling_max *rm) { rm->p_head = 0; rm->np = 0; - rm->nelem = 0; - rm->elem_head = 0; -} - -void rolling_max_push(struct rolling_max *rm, int val) { -#define IDX(n) ((n) % rm->window_size) - if (rm->nelem == rm->window_size) { - auto old_head = rm->elem_head; - // Discard the oldest element. - // rm->elem.pop_front(); - rm->nelem--; - rm->elem_head = IDX(rm->elem_head + 1); - - // Remove discarded element from the priority queue too. - assert(rm->np); - if (rm->p[rm->p_head] == old_head) { - // rm->p.pop_front() - rm->p_head = IDX(rm->p_head + 1); - rm->np--; - } - } +} - // Add the new element to the queue. - // rm->elem.push_back(val) - rm->elem[IDX(rm->elem_head + rm->nelem)] = val; - rm->nelem++; +#define IDX(n) ((n) % rm->capacity) +/// Remove the oldest element in the window. The caller must maintain the list of elements +/// themselves, i.e. the behavior is undefined if `front` does not 1match the oldest +/// element. +void rolling_max_pop_front(struct rolling_max *rm, int front) { + if (rm->p[rm->p_head] == front) { + // rm->p.pop_front() + rm->p_head = IDX(rm->p_head + 1); + rm->np--; + } +} +void rolling_max_push_back(struct rolling_max *rm, int val) { // Update the prority queue. // Remove all elements smaller than the new element from the queue. Because // the new element will become the maximum element before them, and since they @@ -130,7 +148,7 @@ void rolling_max_push(struct rolling_max *rm, int val) { // element, so they will never become the maximum element. while (rm->np) { int p_tail = IDX(rm->p_head + rm->np - 1); - if (rm->elem[rm->p[p_tail]] > val) { + if (rm->p[p_tail] > val) { break; } // rm->p.pop_back() @@ -138,108 +156,119 @@ void rolling_max_push(struct rolling_max *rm, int val) { } // Add the new element to the end of the queue. // rm->p.push_back(rm->start_index + rm->nelem - 1) - rm->p[IDX(rm->p_head + rm->np)] = IDX(rm->elem_head + rm->nelem - 1); + assert(rm->np < rm->capacity); + rm->p[IDX(rm->p_head + rm->np)] = val; rm->np++; -#undef IDX } +#undef IDX int rolling_max_get_max(struct rolling_max *rm) { if (rm->np == 0) { return INT_MIN; } - return rm->elem[rm->p[rm->p_head]]; + return rm->p[rm->p_head]; } TEST_CASE(rolling_max_test) { #define NELEM 15 + struct rolling_window queue; + rolling_window_init(&queue, 3); auto rm = rolling_max_new(3); const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2}; int max[NELEM] = {0}; for (int i = 0; i < NELEM; i++) { - rolling_max_push(rm, data[i]); + int front; + bool full = rolling_window_push_back(&queue, data[i], &front); + if (full) { + rolling_max_pop_front(rm, front); + } + rolling_max_push_back(rm, data[i]); max[i] = rolling_max_get_max(rm); } + rolling_window_destroy(&queue); + rolling_max_destroy(rm); TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0); #undef NELEM } -/// A rolling average of a stream of integers. -struct rolling_avg { - /// The sum of the elements in the window. - int64_t sum; - - /// The elements in the window. - int *elem; - int head, nelem; +// Find the k-th smallest element in an array. +int quickselect(int *elems, int nelem, int k) { + int l = 0, r = nelem; // [l, r) is the range of candidates + while (l != r) { + int pivot = elems[l]; + int i = l, j = r; + while (i < j) { + while (i < j && elems[--j] >= pivot) { + } + elems[i] = elems[j]; + while (i < j && elems[++i] <= pivot) { + } + elems[j] = elems[i]; + } + elems[i] = pivot; - int window_size; -}; + if (i == k) { + break; + } -struct rolling_avg *rolling_avg_new(int size) { - auto rm = ccalloc(1, struct rolling_avg); - if (!rm) { - return NULL; + if (i < k) { + l = i + 1; + } else { + r = i; + } } + return elems[k]; +} - rm->elem = ccalloc(size, int); - rm->window_size = size; - if (!rm->elem) { - free(rm); - return NULL; - } +void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk) { + *rq = (struct rolling_quantile){0}; + rq->tmp_buffer = malloc(sizeof(int) * (size_t)capacity); + rq->capacity = capacity; + rq->min_target_rank = mink; + rq->max_target_rank = maxk; +} - return rm; +void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size, + double target, double tolerance) { + rolling_quantile_init(rq, window_size, (int)((target - tolerance) * window_size), + (int)((target + tolerance) * window_size)); } -void rolling_avg_destroy(struct rolling_avg *rm) { - free(rm->elem); - free(rm); +void rolling_quantile_reset(struct rolling_quantile *rq) { + rq->current_rank = 0; + rq->estimate = 0; } -void rolling_avg_reset(struct rolling_avg *ra) { - ra->sum = 0; - ra->nelem = 0; - ra->head = 0; +void rolling_quantile_destroy(struct rolling_quantile *rq) { + free(rq->tmp_buffer); } -void rolling_avg_push(struct rolling_avg *ra, int val) { - if (ra->nelem == ra->window_size) { - // Discard the oldest element. - // rm->elem.pop_front(); - ra->sum -= ra->elem[ra->head % ra->window_size]; - ra->nelem--; - ra->head = (ra->head + 1) % ra->window_size; +int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements) { + if (rq->current_rank < rq->min_target_rank || rq->current_rank > rq->max_target_rank) { + if (elements->nelem != elements->window_size) { + return INT_MIN; + } + // Re-estimate the quantile. + assert(elements->nelem <= rq->capacity); + rolling_window_copy_to_array(elements, rq->tmp_buffer); + const int target_rank = + rq->min_target_rank + (rq->max_target_rank - rq->min_target_rank) / 2; + rq->estimate = quickselect(rq->tmp_buffer, elements->nelem, target_rank); + rq->current_rank = target_rank; } - - // Add the new element to the queue. - // rm->elem.push_back(val) - ra->elem[(ra->head + ra->nelem) % ra->window_size] = val; - ra->sum += val; - ra->nelem++; + return rq->estimate; } -double rolling_avg_get_avg(struct rolling_avg *ra) { - if (ra->nelem == 0) { - return 0; +void rolling_quantile_push_back(struct rolling_quantile *rq, int x) { + if (x <= rq->estimate) { + rq->current_rank++; } - return (double)ra->sum / (double)ra->nelem; } -TEST_CASE(rolling_avg_test) { -#define NELEM 15 - auto rm = rolling_avg_new(3); - const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; - const double expected_avg[NELEM] = { - 1, 1.5, 2, 2, 8.0 / 3.0, 10.0 / 3.0, 11.0 / 3.0, 10.0 / 3.0, - 11.0 / 3.0, 14.0 / 3.0, 5, 4, 3, 5.0 / 3.0, 2.0 / 3.0}; - double avg[NELEM] = {0}; - for (int i = 0; i < NELEM; i++) { - rolling_avg_push(rm, data[i]); - avg[i] = rolling_avg_get_avg(rm); - } - for (int i = 0; i < NELEM; i++) { - TEST_EQUAL(avg[i], expected_avg[i]); +void rolling_quantile_pop_front(struct rolling_quantile *rq, int x) { + if (x <= rq->estimate) { + rq->current_rank--; } } diff --git a/src/utils.h b/src/utils.h index b7c452912e..fc6dd306de 100644 --- a/src/utils.h +++ b/src/utils.h @@ -17,6 +17,7 @@ #include #include "compiler.h" +#include "log.h" #include "types.h" #define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) @@ -289,20 +290,97 @@ static inline void free_charpp(char **str) { /// int next_power_of_two(int n); +struct rolling_window { + int *elem; + int elem_head, nelem; + int window_size; +}; + +void rolling_window_destroy(struct rolling_window *rw); +void rolling_window_reset(struct rolling_window *rw); +void rolling_window_init(struct rolling_window *rw, int size); +int rolling_window_pop_front(struct rolling_window *rw); +bool rolling_window_push_back(struct rolling_window *rw, int val, int *front); + +/// Copy the contents of the rolling window to an array. The array is assumed to +/// have enough space to hold the contents of the rolling window. +static inline void attr_unused rolling_window_copy_to_array(struct rolling_window *rw, + int *arr) { + // The length from head to the end of the array + auto head_len = (size_t)(rw->window_size - rw->elem_head); + if (head_len >= (size_t)rw->nelem) { + memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * (size_t)rw->nelem); + } else { + auto tail_len = (size_t)((size_t)rw->nelem - head_len); + memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * head_len); + memcpy(arr + head_len, rw->elem, sizeof(int) * tail_len); + } +} + struct rolling_max; -struct rolling_max *rolling_max_new(int window_size); +struct rolling_max *rolling_max_new(int capacity); void rolling_max_destroy(struct rolling_max *rm); void rolling_max_reset(struct rolling_max *rm); -void rolling_max_push(struct rolling_max *rm, int val); +void rolling_max_pop_front(struct rolling_max *rm, int front); +void rolling_max_push_back(struct rolling_max *rm, int val); int rolling_max_get_max(struct rolling_max *rm); -struct rolling_avg; -struct rolling_avg *rolling_avg_new(int window_size); -void rolling_avg_destroy(struct rolling_avg *ra); -void rolling_avg_reset(struct rolling_avg *ra); -void rolling_avg_push(struct rolling_avg *ra, int val); -double rolling_avg_get_avg(struct rolling_avg *ra); +/// Estimate the mean and variance of random variable X using Welford's online +/// algorithm. +struct cumulative_mean_and_var { + double mean; + double m2; + unsigned int n; +}; + +static inline attr_unused void +cumulative_mean_and_var_init(struct cumulative_mean_and_var *cmv) { + *cmv = (struct cumulative_mean_and_var){0}; +} + +static inline attr_unused void +cumulative_mean_and_var_update(struct cumulative_mean_and_var *cmv, double x) { + if (cmv->n == UINT_MAX) { + // We have too many elements, let's keep the mean and variance. + return; + } + cmv->n++; + double delta = x - cmv->mean; + cmv->mean += delta / (double)cmv->n; + cmv->m2 += delta * (x - cmv->mean); +} + +static inline attr_unused double +cumulative_mean_and_var_get_var(struct cumulative_mean_and_var *cmv) { + if (cmv->n < 2) { + return 0; + } + return cmv->m2 / (double)(cmv->n - 1); +} + +// Find the k-th smallest element in an array. +int quickselect(int *elems, int nelem, int k); + +/// A naive quantile estimator. +/// +/// Estimates the N-th percentile of a random variable X in a sliding window. +struct rolling_quantile { + int current_rank; + int min_target_rank, max_target_rank; + int estimate; + int capacity; + int *tmp_buffer; +}; + +void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk); +void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size, + double target, double tolerance); +void rolling_quantile_reset(struct rolling_quantile *rq); +void rolling_quantile_destroy(struct rolling_quantile *rq); +int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements); +void rolling_quantile_push_back(struct rolling_quantile *rq, int x); +void rolling_quantile_pop_front(struct rolling_quantile *rq, int x); // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. From b6e7ea5639154078b136b263d7a28a250359b30b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 10 Jun 2023 14:11:30 +0100 Subject: [PATCH 071/177] core: don't update render statistics if we didn't actually render Sometimes a scheduled render can end up doing nothing, e.g. if the damage region is empty. In that case we don't have valid data to collect and thus shouldn't update the statistics. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 1 + src/common.h | 3 +++ src/picom.c | 13 ++++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index bacf29d28b..36f80cc685 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -189,6 +189,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { ps->last_schedule_delay = now_us - ps->next_render; } } + ps->did_render = true; if (ps->backend_data->ops->prepare) { ps->backend_data->ops->prepare(ps->backend_data, ®_paint); diff --git a/src/common.h b/src/common.h index 65d0e7b65f..e183a1e6b0 100644 --- a/src/common.h +++ b/src/common.h @@ -255,6 +255,9 @@ typedef struct session { uint64_t last_schedule_delay; /// When do we want our next frame to start rendering. uint64_t next_render; + /// Did we actually render the last frame. Sometimes redraw will be scheduled only + /// to find out nothing has changed. In which case this will be set to false. + bool did_render; /// Whether we can perform frame pacing. bool frame_pacing; diff --git a/src/picom.c b/src/picom.c index 8bb22d7d0e..36c8a34af3 100644 --- a/src/picom.c +++ b/src/picom.c @@ -240,12 +240,15 @@ void schedule_render(session_t *ps, bool triggered_by_vblank) { (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); if (ps->target_msc == ps->last_msc) { // The frame has just been displayed, record its render time; - log_trace("Last render call took: %d (gpu) + %d (cpu) us, " - "last_msc: %" PRIu64, - render_time_us, (int)ps->last_schedule_delay, ps->last_msc); - render_statistics_add_render_time_sample( - &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); + if (ps->did_render) { + log_trace("Last render call took: %d (gpu) + %d (cpu) us, " + "last_msc: %" PRIu64, + render_time_us, (int)ps->last_schedule_delay, ps->last_msc); + render_statistics_add_render_time_sample( + &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); + } ps->target_msc = 0; + ps->did_render = false; ps->last_schedule_delay = 0; } From 7d9692360b2eaa2068a465819905f3322f9d57fa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 10 Jun 2023 14:18:25 +0100 Subject: [PATCH 072/177] core: use SCHED_RR scheduling Make picom realtime to reduce latency, and make rendering times more predictable to help pacing. Signed-off-by: Yuxuan Shui --- src/picom.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/picom.c b/src/picom.c index 36c8a34af3..d6b52b589b 100644 --- a/src/picom.c +++ b/src/picom.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -2553,6 +2554,31 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(ps); return NULL; } +void set_rr_scheduling(void) { + struct rlimit rlim; + if (getrlimit(RLIMIT_RTPRIO, &rlim) != 0) { + log_warn("Failed to get RLIMIT_RTPRIO, not setting real-time priority"); + return; + } + int old_policy; + int ret; + struct sched_param param; + + ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); + if (ret != 0) { + log_debug("Failed to get old scheduling priority"); + return; + } + + param.sched_priority = (int)rlim.rlim_cur; + + ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + if (ret != 0) { + log_info("Failed to set scheduling priority to %lu", rlim.rlim_cur); + return; + } + log_info("Set scheduling priority to %lu", rlim.rlim_cur); +} /** * Destroy a session. @@ -2760,6 +2786,7 @@ static void session_destroy(session_t *ps) { * @param ps current session */ static void session_run(session_t *ps) { + set_rr_scheduling(); // In benchmark mode, we want draw_timer handler to always be active if (ps->o.benchmark) { ev_timer_set(&ps->draw_timer, 0, 0); From 6a69cdb002dc6943fd228d73f8b952681b2894b9 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 10 Jun 2023 14:28:43 +0100 Subject: [PATCH 073/177] options: add a no-frame-pacing option Signed-off-by: Yuxuan Shui --- man/picom.1.asciidoc | 3 +++ src/config.c | 1 + src/config.h | 2 ++ src/config_libconfig.c | 3 +++ src/options.c | 2 ++ src/picom.c | 6 +++--- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 0a2e3c31a5..1ccf093390 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -109,6 +109,9 @@ OPTIONS *--rounded-corners-exclude* 'CONDITION':: Exclude conditions for rounded corners. +*--no-frame-pacing*:: + Disable vsync-aware frame pacing. By default, the compositor tries to make sure it only renders once per vblank interval, and also the render happens as late as possible to minimize the latency from updates to the screen. However this can sometimes cause stuttering, or even lowered frame rate. This option can be used to disable frame pacing. + *--mark-wmwin-focused*:: Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active. diff --git a/src/config.c b/src/config.c index 1013d128fa..3797ffa4a1 100644 --- a/src/config.c +++ b/src/config.c @@ -742,6 +742,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .logpath = NULL, .use_damage = true, + .no_frame_pacing = false, .shadow_red = 0.0, .shadow_green = 0.0, diff --git a/src/config.h b/src/config.h index 5ce3ba9635..31e6774c8f 100644 --- a/src/config.h +++ b/src/config.h @@ -140,6 +140,8 @@ typedef struct options { bool vsync_use_glfinish; /// Whether use damage information to help limit the area to paint bool use_damage; + /// Disable frame pacing + bool no_frame_pacing; // === Shadow === /// Red, green and blue tone of the shadow. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 63f05412bf..40bc47b838 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -363,6 +363,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --corner-radius-rules parse_cfg_condlst_corner(opt, &cfg, "corner-radius-rules"); + // --no-frame-pacing + lcfg_lookup_bool(&cfg, "no-frame-pacing", &opt->no_frame_pacing); + // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) diff --git a/src/options.c b/src/options.c index 9d500b8e65..b0d956a2bc 100644 --- a/src/options.c +++ b/src/options.c @@ -176,6 +176,7 @@ static const struct picom_option picom_options[] = { "rendered screen. Reduces banding artifacts, but might cause performance " "degradation. Only works with OpenGL."}, // 340 is corner-radius-rules + {"no-frame-pacing" , no_argument , 341, NULL , "Disable frame pacing. This might increase the latency."}, {"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."}, {"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."}, {"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"}, @@ -738,6 +739,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --dithered-present opt->dithered_present = true; break; + P_CASEBOOL(341, no_frame_pacing); P_CASEBOOL(733, legacy_backends); P_CASEBOOL(800, monitor_repaint); case 801: diff --git a/src/picom.c b/src/picom.c index d6b52b589b..fb7e49e2e2 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1445,9 +1445,9 @@ static bool redirect_start(session_t *ps) { pixman_region32_init(&ps->damage_ring[i]); } - ps->frame_pacing = true; - if (ps->o.legacy_backends || ps->o.benchmark || - !ps->backend_data->ops->last_render_time) { + ps->frame_pacing = !ps->o.no_frame_pacing; + if ((ps->o.legacy_backends || ps->o.benchmark || !ps->backend_data->ops->last_render_time) && + ps->frame_pacing) { // Disable frame pacing if we are using a legacy backend or if we are in // benchmark mode, or if the backend doesn't report render time log_info("Disabling frame pacing."); From d0c121ec834a4c837ab78f574ece5f547e374054 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 10 Jun 2023 23:12:31 +0100 Subject: [PATCH 074/177] core: print some timing info from draw_callback_impl Signed-off-by: Yuxuan Shui --- src/picom.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/picom.c b/src/picom.c index fb7e49e2e2..8a58fe50f7 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1741,8 +1741,24 @@ static void handle_pending_updates(EV_P_ struct session *ps) { } static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { + struct timespec now; + int64_t draw_callback_enter_us; + clock_gettime(CLOCK_MONOTONIC, &now); + + draw_callback_enter_us = (now.tv_sec * 1000000LL + now.tv_nsec / 1000); + if (ps->next_render != 0) { + log_trace("Schedule delay: %" PRIi64 " us", + draw_callback_enter_us - (int64_t)ps->next_render); + } + handle_pending_updates(EV_A_ ps); + int64_t after_handle_pending_updates_us; + clock_gettime(CLOCK_MONOTONIC, &now); + after_handle_pending_updates_us = (now.tv_sec * 1000000LL + now.tv_nsec / 1000); + log_trace("handle_pending_updates took: %" PRIi64 " us", + after_handle_pending_updates_us - draw_callback_enter_us); + if (ps->first_frame) { // If we are still rendering the first frame, if some of the windows are // unmapped/destroyed during the above handle_pending_updates() call, they @@ -1799,6 +1815,12 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { ev_timer_start(EV_A_ & ps->fade_timer); } + int64_t after_preprocess_us; + clock_gettime(CLOCK_MONOTONIC, &now); + after_preprocess_us = (now.tv_sec * 1000000LL + now.tv_nsec / 1000); + log_trace("paint_preprocess took: %" PRIi64 " us", + after_preprocess_us - after_handle_pending_updates_us); + // If the screen is unredirected, free all_damage to stop painting if (ps->redirected && ps->o.stoppaint_force != ON) { static int paint = 0; From 6af0251f2a856e2f62170cf317363eb95ce8bc33 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 12 Jun 2023 11:49:06 +0100 Subject: [PATCH 075/177] readme: add some badges and a link to our discord Signed-off-by: Yuxuan Shui --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fecde197e..5372111362 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ picom ===== +[![circleci](https://circleci.com/gh/yshui/picom.svg?style=shield)](https://circleci.com/gh/yshui/picom) +[![codecov](https://codecov.io/gh/yshui/picom/branch/next/graph/badge.svg?token=NRSegi0Gze)](https://codecov.io/gh/yshui/picom) +[![chat on discord](https://img.shields.io/discord/1106224720833159198?logo=discord)](https://discord.gg/uqmNX6dR) + __picom__ is a compositor for X, and a [fork of Compton](History.md). **This is a development branch, bugs to be expected** -You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions). +You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions), or chat with other users on [discord](https://discord.gg/uqmNX6dR)! ## Change Log From 5b6f6ecbf5877ca18eb7c028247f67650959e5ef Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 12 Jun 2023 12:03:09 +0100 Subject: [PATCH 076/177] backend: egl: print egl error when eglCreateImage failed Signed-off-by: Yuxuan Shui --- src/backend/gl/egl.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index f1f67d91e6..9f651544a0 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -42,6 +42,30 @@ static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL; static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL; static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL; +const char *eglGetErrorString(EGLint error) { +#define CASE_STR(value) \ + case value: return #value; + switch (error) { + CASE_STR(EGL_SUCCESS) + CASE_STR(EGL_NOT_INITIALIZED) + CASE_STR(EGL_BAD_ACCESS) + CASE_STR(EGL_BAD_ALLOC) + CASE_STR(EGL_BAD_ATTRIBUTE) + CASE_STR(EGL_BAD_CONTEXT) + CASE_STR(EGL_BAD_CONFIG) + CASE_STR(EGL_BAD_CURRENT_SURFACE) + CASE_STR(EGL_BAD_DISPLAY) + CASE_STR(EGL_BAD_SURFACE) + CASE_STR(EGL_BAD_MATCH) + CASE_STR(EGL_BAD_PARAMETER) + CASE_STR(EGL_BAD_NATIVE_PIXMAP) + CASE_STR(EGL_BAD_NATIVE_WINDOW) + CASE_STR(EGL_CONTEXT_LOST) + default: return "Unknown"; + } +#undef CASE_STR +} + /** * Free a glx_texture_t. */ @@ -283,7 +307,8 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b eglpixmap->owned = owned; if (eglpixmap->image == EGL_NO_IMAGE) { - log_error("Failed to create eglpixmap for pixmap %#010x", pixmap); + log_error("Failed to create eglpixmap for pixmap %#010x: %s", pixmap, + eglGetErrorString(eglGetError())); goto err; } From 5826adf8533c81086371af1c309600f8660800e5 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 12 Jun 2023 21:17:28 +0300 Subject: [PATCH 077/177] backend: gl: remove gl_delete_texture function it wasn't used and it's hard to call it a shortcut --- src/backend/gl/gl_common.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index f7e721c289..5e3a505d0b 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -188,10 +188,6 @@ void gl_present(backend_t *base, const region_t *); bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output); enum device_status gl_device_status(backend_t *base); -static inline void gl_delete_texture(GLuint texture) { - glDeleteTextures(1, &texture); -} - /** * Get a textual representation of an OpenGL error. */ From 8245de27ed7c329e710c0569021c2829ed9d4fd1 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 12 Jun 2023 21:24:57 +0300 Subject: [PATCH 078/177] backend: gl: do not leak back and default mask textures they're generated during backend initialization and now deleted during deinitialization --- src/backend/gl/gl_common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 42f7136dc6..df502dc0e1 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -976,6 +976,9 @@ void gl_deinit(struct gl_data *gd) { gd->default_shader = NULL; } + glDeleteTextures(1, &gd->default_mask_texture); + glDeleteTextures(1, &gd->back_texture); + gl_check_err(); } From 1e398b9c24fdbf8db76190f51f82b901b15f93de Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 12 Jun 2023 23:11:20 +0300 Subject: [PATCH 079/177] backend: dummy: fix a typo in the dummy_get_blur_size function "reisze_region" => "resize_region" --- src/backend/dummy/dummy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 7e06facecc..0c39741949 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -162,7 +162,7 @@ void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx } void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { - // These numbers are arbitrary, to make sure the reisze_region code path is + // These numbers are arbitrary, to make sure the resize_region code path is // covered. *width = 5; *height = 5; From 223872bc7f65274af24cb441e3ac50dd3ec50bcf Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 12 Jun 2023 23:41:25 +0300 Subject: [PATCH 080/177] backend: dummy: do not leak owned pixmaps free pixmaps which ownership was transferred to the backend --- src/backend/dummy/dummy.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 0c39741949..5f5e1229a7 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -17,6 +17,7 @@ struct dummy_image { xcb_pixmap_t pixmap; bool transparent; int *refcount; + bool owned; UT_hash_handle hh; }; @@ -42,6 +43,9 @@ void dummy_deinit(struct backend_base *data) { log_warn("Backend image for pixmap %#010x is not freed", img->pixmap); HASH_DEL(dummy->images, img); free(img->refcount); + if (img->owned) { + xcb_free_pixmap(data->c, img->pixmap); + } free(img); } free(dummy); @@ -82,7 +86,7 @@ bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity at } void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, - struct xvisual_info fmt, bool owned attr_unused) { + struct xvisual_info fmt, bool owned) { auto dummy = (struct dummy_data *)base; struct dummy_image *img = NULL; HASH_FIND_INT(dummy->images, &pixmap, img); @@ -96,6 +100,7 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, img->transparent = fmt.alpha_size != 0; img->refcount = ccalloc(1, int); *img->refcount = 1; + img->owned = owned; HASH_ADD_INT(dummy->images, pixmap, img); return (void *)img; @@ -112,6 +117,9 @@ void dummy_release_image(backend_t *base, void *image) { if (*img->refcount == 0) { HASH_DEL(dummy->images, img); free(img->refcount); + if (img->owned) { + xcb_free_pixmap(base->c, img->pixmap); + } free(img); } } From 99568446472c8eed9faa7687b2f94aa29302d979 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 14 Jun 2023 00:22:54 +0100 Subject: [PATCH 081/177] backend: log more timing info Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index 36f80cc685..da1fa42996 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui +#include #include #include @@ -82,6 +83,9 @@ void handle_device_reset(session_t *ps) { /// paint all windows void paint_all_new(session_t *ps, struct managed_win *t) { + struct timespec now = get_time_timespec(); + auto paint_all_start_us = + (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000; if (ps->backend_data->ops->device_status && ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) { return handle_device_reset(ps); @@ -96,6 +100,12 @@ void paint_all_new(session_t *ps, struct managed_win *t) { ps->xsync_exists = false; } } + + now = get_time_timespec(); + auto after_sync_fence_us = + (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000; + log_trace("Time spent on sync fence: %" PRIu64 " us", + after_sync_fence_us - paint_all_start_us); // 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 @@ -176,17 +186,17 @@ void paint_all_new(session_t *ps, struct managed_win *t) { region_t reg_shadow_clip; pixman_region32_init(®_shadow_clip); - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - auto now_us = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000); + now = get_time_timespec(); + auto after_damage_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000; + log_trace("Getting damage took %" PRIu64 " us", after_damage_us - after_sync_fence_us); if (ps->next_render > 0) { - log_trace("Render schedule deviation: %ld us (%s) %ld %ld", - labs((int64_t)now_us - (int64_t)ps->next_render), - now_us < ps->next_render ? "early" : "late", now_us, - ps->next_render); + log_trace("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld", + labs((long)after_damage_us - (long)ps->next_render), + after_damage_us < ps->next_render ? "early" : "late", + after_damage_us, ps->next_render); ps->last_schedule_delay = 0; - if (now_us > ps->next_render) { - ps->last_schedule_delay = now_us - ps->next_render; + if (after_damage_us > ps->next_render) { + ps->last_schedule_delay = after_damage_us - ps->next_render; } } ps->did_render = true; From 0deaa9a241fccb30ba363c3b071028b1f846de0a Mon Sep 17 00:00:00 2001 From: Kurenshe Nurdaulet <63652620+EpsilonKu@users.noreply.github.com> Date: Thu, 15 Jun 2023 13:23:49 +0600 Subject: [PATCH 082/177] Update discord link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5372111362..b7ea2a18e7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ __picom__ is a compositor for X, and a [fork of Compton](History.md). **This is a development branch, bugs to be expected** -You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions), or chat with other users on [discord](https://discord.gg/uqmNX6dR)! +You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions), or chat with other users on [discord](https://discord.gg/SY5JJzPgME)! ## Change Log From 4d724047ef0e8ce713e9d4d51773a3af4146b3a3 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 16 Jun 2023 17:57:59 +0300 Subject: [PATCH 083/177] options: handle max-brightness option better allow it's use with the egl backend and report it's unavailability better --- src/options.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/options.c b/src/options.c index 1fe955adbe..2ee504e91a 100644 --- a/src/options.c +++ b/src/options.c @@ -824,18 +824,16 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->inactive_dim = normalize_d(opt->inactive_dim); opt->frame_opacity = normalize_d(opt->frame_opacity); opt->shadow_opacity = normalize_d(opt->shadow_opacity); - opt->max_brightness = normalize_d(opt->max_brightness); if (opt->max_brightness < 1.0) { - if (opt->use_damage) { - log_warn("--max-brightness requires --no-use-damage. Falling " - "back to 1.0"); + if (opt->backend == BKEND_XRENDER || opt->legacy_backends) { + log_warn("--max-brightness is not supported by the %s backend. " + "Falling back to 1.0.", + opt->backend == BKEND_XRENDER ? "xrender" : "legacy glx"); opt->max_brightness = 1.0; - } - - if (opt->legacy_backends || opt->backend != BKEND_GLX) { - log_warn("--max-brightness requires the new glx " - "backend. Falling back to 1.0"); + } else if (opt->use_damage) { + log_warn("--max-brightness requires --no-use-damage. Falling " + "back to 1.0."); opt->max_brightness = 1.0; } } From 9f9cff3b0669b4dad7050e928b9bf8789c2252a0 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 16 Jun 2023 18:21:34 +0300 Subject: [PATCH 084/177] options: unify unavailability reporting of some options unify unavailability reporting of the max-brightness, window-shader-fg and window-shader-fg-rule options --- src/options.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/options.c b/src/options.c index 2ee504e91a..2b0bb1771e 100644 --- a/src/options.c +++ b/src/options.c @@ -805,10 +805,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, if (opt->window_shader_fg || opt->window_shader_fg_rules) { if (opt->backend == BKEND_XRENDER || opt->legacy_backends) { log_warn(opt->backend == BKEND_XRENDER - ? "Shader interface is not available for the " - "xrender backend." - : "The new shader interface is not available for " - "the legacy glx backend. You may want to use " + ? "Shader interface is not supported by the xrender " + "backend." + : "The new shader interface is not supported by the " + "legacy glx backend. You may want to use " "--glx-fshader-win instead."); opt->window_shader_fg = NULL; c2_list_free(&opt->window_shader_fg_rules, free); From ecbc8b50edd81c8fd1f19570590dd78fd4647d20 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 16 Jun 2023 19:42:36 +0300 Subject: [PATCH 085/177] backend: xrender: set created picture to repeat when binding a pixmap --- src/backend/xrender/xrender.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 686e6deb8c..0041817aca 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -520,8 +520,9 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool inner->height = img->base.eheight = r->height; inner->pixmap = pixmap; inner->has_alpha = fmt.alpha_size != 0; - inner->pict = - x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL); + xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_NORMAL}; + inner->pict = x_create_picture_with_visual_and_pixmap( + base->c, fmt.visual, pixmap, XCB_RENDER_CP_REPEAT, &pic_attrs); inner->owned = owned; inner->visual = fmt.visual; inner->refcount = 1; From 07303ce2cb13f207df69b05a4b012a1bbb5263cb Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Fri, 16 Jun 2023 23:50:20 +0200 Subject: [PATCH 086/177] core: added proper event handling for XESetWireToEvent --- src/event.c | 5 +++-- src/meson.build | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event.c b/src/event.c index afaa02c490..da350b95df 100644 --- a/src/event.c +++ b/src/event.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "atom.h" #include "common.h" @@ -687,9 +688,9 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // For even more details, see: // https://bugs.freedesktop.org/show_bug.cgi?id=35945 // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html - auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0); + auto proc = XESetWireToEvent(ps->dpy, XCB_EVENT_RESPONSE_TYPE(ev), 0); if (proc) { - XESetWireToEvent(ps->dpy, ev->response_type, proc); + XESetWireToEvent(ps->dpy, XCB_EVENT_RESPONSE_TYPE(ev), proc); XEvent dummy; // Stop Xlib from complaining about lost sequence numbers. diff --git a/src/meson.build b/src/meson.build index 0cd87c896d..a7f25b0231 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,6 +31,8 @@ foreach i : required_xcb_packages base_deps += [dependency(i, version: '>=1.12.0', required: true)] endforeach +base_deps += [dependency('xcb-util', version: '>=0.4.0', required: true)] + if not cc.has_header('uthash.h') error('Dependency uthash not found') endif From 56745b64d7a2b4875ce3fa14dc168fd1314ece9f Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Sat, 17 Jun 2023 01:08:47 +0200 Subject: [PATCH 087/177] core: event code refactoring --- src/event.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/event.c b/src/event.c index da350b95df..b140082874 100644 --- a/src/event.c +++ b/src/event.c @@ -107,7 +107,7 @@ static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_ static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) { static char buf[128]; - switch (ev->response_type & 0x7f) { + switch (XCB_EVENT_RESPONSE_TYPE(ev)) { CASESTRRET(FocusIn); CASESTRRET(FocusOut); CASESTRRET(CreateNotify); @@ -666,7 +666,7 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { } void ev_handle(session_t *ps, xcb_generic_event_t *ev) { - if ((ev->response_type & 0x7f) != KeymapNotify) { + if (XCB_EVENT_RESPONSE_TYPE(ev) != KeymapNotify) { discard_pending(ps, ev->full_sequence); } @@ -688,9 +688,10 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // For even more details, see: // https://bugs.freedesktop.org/show_bug.cgi?id=35945 // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html - auto proc = XESetWireToEvent(ps->dpy, XCB_EVENT_RESPONSE_TYPE(ev), 0); + auto response_type = XCB_EVENT_RESPONSE_TYPE(ev); + auto proc = XESetWireToEvent(ps->dpy, response_type, 0); if (proc) { - XESetWireToEvent(ps->dpy, XCB_EVENT_RESPONSE_TYPE(ev), proc); + XESetWireToEvent(ps->dpy, response_type, proc); XEvent dummy; // Stop Xlib from complaining about lost sequence numbers. @@ -709,6 +710,7 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // XXX redraw needs to be more fine grained queue_redraw(ps); + // the events sent from SendEvent will be ignored switch (ev->response_type) { case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break; case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break; From c065ad1b8d8f7b0dc1db2b9f5a716d96440a62d4 Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Sat, 17 Jun 2023 01:15:38 +0200 Subject: [PATCH 088/177] Refactored meson.build --- src/meson.build | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/meson.build b/src/meson.build index a7f25b0231..98c50664a3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,7 +20,7 @@ required_xcb_packages = [ ] required_packages = [ - 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1' + 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1', 'xcb-util' ] foreach i : required_packages @@ -31,8 +31,6 @@ foreach i : required_xcb_packages base_deps += [dependency(i, version: '>=1.12.0', required: true)] endforeach -base_deps += [dependency('xcb-util', version: '>=0.4.0', required: true)] - if not cc.has_header('uthash.h') error('Dependency uthash not found') endif From 73a366ffe04484b68e7596d89dd0e28ceb787286 Mon Sep 17 00:00:00 2001 From: Monsterovich Date: Sat, 17 Jun 2023 01:27:20 +0200 Subject: [PATCH 089/177] Added xcb-util to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b7ea2a18e7..5b358bbe6b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * libXext * xproto * xcb +* xcb-util * xcb-damage * xcb-dpms * xcb-xfixes From c02a1e2a30943d61db0586a26d410995261c68c4 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 17 Jun 2023 16:41:15 +0300 Subject: [PATCH 090/177] ci: install the xcb-util dependency the xcb-util dependency that was introduced recently isn't installed by the github's ci workflow what results in ci failures --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2e3dfe7e26..c9ebfa6bc4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libxcb-util-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build if: ${{ matrix.language == 'cpp' }} # Autobuild From 6b6a8da03510ad38d7f7c89092b90f681c673de4 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 14:55:22 +0300 Subject: [PATCH 091/177] render: remove the unused background_props_str external constant --- src/render.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/render.c b/src/render.c index 80cb59a6fa..f4b3a97989 100644 --- a/src/render.c +++ b/src/render.c @@ -583,8 +583,6 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) } } -extern const char *background_props_str[]; - static bool get_root_tile(session_t *ps) { /* if (ps->o.paint_on_overlay) { From fea1dc794c19131f7f4eae2f4a0345516c615781 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 14:57:50 +0300 Subject: [PATCH 092/177] atom: add atoms associated with the background pixmap --- src/atom.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/atom.h b/src/atom.h index 6f4eae69f7..5ea7701c4b 100644 --- a/src/atom.h +++ b/src/atom.h @@ -3,8 +3,8 @@ #include -#include "meta.h" #include "cache.h" +#include "meta.h" // clang-format off // Splitted into 2 lists because of the limitation of our macros @@ -23,7 +23,10 @@ WM_CLIENT_MACHINE, \ _NET_ACTIVE_WINDOW, \ _COMPTON_SHADOW, \ - _NET_WM_WINDOW_TYPE + _NET_WM_WINDOW_TYPE, \ + _XROOTPMAP_ID, \ + ESETROOT_PMAP_ID, \ + _XSETROOT_ID #define ATOM_LIST2 \ _NET_WM_WINDOW_TYPE_DESKTOP, \ From cbd2d4125c907c937d76afefce16e845917a2914 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 15:32:15 +0300 Subject: [PATCH 093/177] x: rewrite x_is_root_back_pixmap_atom using root back pixmap atoms --- src/x.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/x.c b/src/x.c index 6121e77344..8c4cabe61d 100644 --- a/src/x.c +++ b/src/x.c @@ -656,13 +656,8 @@ x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atom } bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) { - for (int p = 0; background_props_str[p]; p++) { - xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]); - if (prop_atom == atom) { - return true; - } - } - return false; + return atom == atoms->a_XROOTPMAP_ID || atom == atoms->aESETROOT_PMAP_ID || + atom == atoms->a_XSETROOT_ID; } /** From 550518c5d122c49c8fcedb31c16eeb2d723e67ee Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 15:45:06 +0300 Subject: [PATCH 094/177] x: rewrite x_get_root_back_pixmap using root back pixmap atoms and remove the now unused background_props_str constant --- src/x.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/x.c b/src/x.c index 8c4cabe61d..c902e001e4 100644 --- a/src/x.c +++ b/src/x.c @@ -628,22 +628,15 @@ bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) { free(r); return ret; } -/// Names of root window properties that could point to a pixmap of -/// background. -static const char *background_props_str[] = { - "_XROOTPMAP_ID", - "_XSETROOT_ID", - 0, -}; xcb_pixmap_t x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) { xcb_pixmap_t pixmap = XCB_NONE; - // Get the values of background attributes - for (int p = 0; background_props_str[p]; p++) { - xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]); - winprop_t prop = x_get_prop(c, root, prop_atom, 1, XCB_ATOM_PIXMAP, 32); + xcb_atom_t root_back_pixmap_atoms[] = {atoms->a_XROOTPMAP_ID, atoms->aESETROOT_PMAP_ID}; + for (size_t i = 0; i < ARR_SIZE(root_back_pixmap_atoms); i++) { + winprop_t prop = + x_get_prop(c, root, root_back_pixmap_atoms[i], 1, XCB_ATOM_PIXMAP, 32); if (prop.nitems) { pixmap = (xcb_pixmap_t)*prop.p32; free_winprop(&prop); From a377d12a66132871f31f88c713662f27634e188e Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 16:09:29 +0300 Subject: [PATCH 095/177] x: add a comment on the _XSETROOT_ID root window property usage --- src/x.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/x.c b/src/x.c index c902e001e4..a2a7153d74 100644 --- a/src/x.c +++ b/src/x.c @@ -629,6 +629,18 @@ bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) { return ret; } +/// We don't use the _XSETROOT_ID root window property as a source of the background +/// pixmap because it most likely points to a dummy pixmap used to keep the colormap +/// associated with the background pixmap alive but we listen for it's changes and update +/// the background pixmap accordingly. +/// +/// For details on the _XSETROOT_ID root window property and it's usage see: +/// https://metacpan.org/pod/X11::Protocol::XSetRoot#_XSETROOT_ID +/// https://gitlab.freedesktop.org/xorg/app/xsetroot/-/blob/435d35409768de7cbc2c47a6322192dd4b480545/xsetroot.c#L318-352 +/// https://github.com/ImageMagick/ImageMagick/blob/d04a47227637dbb3af9231b0107ccf9677bf985e/MagickCore/xwindow.c#L9203-L9260 +/// https://github.com/ImageMagick/ImageMagick/blob/d04a47227637dbb3af9231b0107ccf9677bf985e/MagickCore/xwindow.c#L1853-L1922 +/// https://www.fvwm.org/Archive/Manpages/fvwm-root.html + xcb_pixmap_t x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) { xcb_pixmap_t pixmap = XCB_NONE; From 689321419b8ea78b939bb718d177457cc961f0b0 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 19:27:22 +0300 Subject: [PATCH 096/177] backend: egl: remove references to the glx backend --- src/backend/gl/egl.c | 12 ++++++------ src/backend/gl/egl.h | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 9f651544a0..761eb63504 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -67,7 +67,7 @@ const char *eglGetErrorString(EGLint error) { } /** - * Free a glx_texture_t. + * Free a gl_texture_t. */ static void egl_release_image(backend_t *base, struct gl_texture *tex) { struct egl_data *gd = (void *)base; @@ -88,14 +88,14 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) { } /** - * Destroy GLX related resources. + * Destroy EGL related resources. */ void egl_deinit(backend_t *base) { struct egl_data *gd = (void *)base; gl_deinit(&gd->gl); - // Destroy GLX context + // Destroy EGL context if (gd->ctx != EGL_NO_CONTEXT) { eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(gd->display, gd->ctx); @@ -225,12 +225,12 @@ static backend_t *egl_init(session_t *ps) { gd->ctx = eglCreateContext(gd->display, config, NULL, NULL); if (gd->ctx == EGL_NO_CONTEXT) { - log_error("Failed to get GLX context."); + log_error("Failed to get EGL context."); goto end; } if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) { - log_error("Failed to attach GLX context."); + log_error("Failed to attach EGL context."); goto end; } @@ -423,7 +423,7 @@ struct backend_operations egl_ops = { PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; /** - * Check if a GLX extension exists. + * Check if a EGL extension exists. */ static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) { const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS); diff --git a/src/backend/gl/egl.h b/src/backend/gl/egl.h index 171b173585..f482032b3e 100644 --- a/src/backend/gl/egl.h +++ b/src/backend/gl/egl.h @@ -1,13 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui #pragma once -#include -// Older version of glx.h defines function prototypes for these extensions... -// Rename them to avoid conflicts #include #include #include #include +#include #include #include From 15667d6b6eaf6ecdc4bc134d12f7e114d0b7dcab Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 19:34:53 +0300 Subject: [PATCH 097/177] backend: gl: remove references to the glx backend --- src/backend/gl/gl_common.c | 2 +- src/backend/gl/gl_common.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index df502dc0e1..ef9a34b89d 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -808,7 +808,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glGenQueries(2, gd->frame_timing); gd->current_frame_timing = 0; - // Initialize GLX data structure + // Initialize GL data structure glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 5e3a505d0b..4de3ad6adb 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -77,7 +77,7 @@ typedef struct { GLint color_loc; } gl_fill_shader_t; -/// @brief Wrapper of a binded GLX texture. +/// @brief Wrapper of a binded GL texture. struct gl_texture { int refcount; bool has_alpha; @@ -214,7 +214,7 @@ static inline const char *gl_get_err_str(GLenum err) { } /** - * Check for GLX error. + * Check for GL error. * * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ */ @@ -225,10 +225,10 @@ static inline void gl_check_err_(const char *func, int line) { const char *errtext = gl_get_err_str(err); if (errtext) { log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "GLX error at line %d: %s", line, errtext); + "GL error at line %d: %s", line, errtext); } else { log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "GLX error at line %d: %d", line, err); + "GL error at line %d: %d", line, err); } } } @@ -265,7 +265,7 @@ static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) #define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb)) /** - * Check if a GLX extension exists. + * Check if a GL extension exists. */ static inline bool gl_has_extension(const char *ext) { int nexts = 0; From b1cae3d367d5ad9186897f84f8645cc4848024e6 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 21:27:31 +0300 Subject: [PATCH 098/177] github: update codeql action's init, autobuild and analyze to v2 addresses this annotation: This version of the CodeQL Action was deprecated on January 18th, 2023, and is no longer updated or supported. For better performance, improved security, and new features, upgrade to v2. For more information, see https://github.blog/changelog/2023-01-18-code-scanning-codeql-action-v1-is-now-deprecated/ partially addresses this annotation: Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: actions/checkout@v2, github/codeql-action/init@v1, github/codeql-action/autobuild@v1, github/codeql-action/analyze@v1. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c9ebfa6bc4..1471844c8c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} @@ -41,7 +41,7 @@ jobs: # Autobuild - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From a5591b33a63ad5e86f659ebd47ba99e07a752184 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 21:33:16 +0300 Subject: [PATCH 099/177] github: update actions' checkout to v3 addresses this annotation: Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: actions/checkout@v2. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/coding-style-pr.yml | 2 +- .github/workflows/coding-style.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1471844c8c..8329bce38b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/coding-style-pr.yml b/.github/workflows/coding-style-pr.yml index c667e3f6fb..5de4c67fcc 100644 --- a/.github/workflows/coding-style-pr.yml +++ b/.github/workflows/coding-style-pr.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} - uses: yshui/git-clang-format-lint@v1.14 with: diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index d378113c4d..49f5379aaa 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 2 - uses: yshui/git-clang-format-lint@v1.14 From 4911cbc24a478d7150ad788f67e68faca7b792dc Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 18 Jun 2023 21:37:41 +0300 Subject: [PATCH 100/177] github: don't checkout the head of a pull request addresses this annotation: 1 issue was detected with this workflow: git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results. --- .github/workflows/codeql-analysis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8329bce38b..c0d5d0f748 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,15 +19,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL From 9295f7e4c7c54088a6b87bab628a0b6350fb14ba Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 24 Jun 2023 02:49:20 +0100 Subject: [PATCH 101/177] core: don't check RLIMIT_RTPRIO FreeBSD doesn't have RLIMIT_RTPRIO. So instead we skip this check and just always try to set our priority to the lowest SCHED_RR priority available. Fixes #1082 Signed-off-by: Yuxuan Shui --- src/picom.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/picom.c b/src/picom.c index 8a58fe50f7..9ddb49de6e 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2577,11 +2577,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, return NULL; } void set_rr_scheduling(void) { - struct rlimit rlim; - if (getrlimit(RLIMIT_RTPRIO, &rlim) != 0) { - log_warn("Failed to get RLIMIT_RTPRIO, not setting real-time priority"); - return; - } + int priority = sched_get_priority_min(SCHED_RR); + int old_policy; int ret; struct sched_param param; @@ -2592,14 +2589,15 @@ void set_rr_scheduling(void) { return; } - param.sched_priority = (int)rlim.rlim_cur; - + param.sched_priority = priority; ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if (ret != 0) { - log_info("Failed to set scheduling priority to %lu", rlim.rlim_cur); + log_info("Failed to set real-time scheduling priority to %d. Consider " + "giving picom the CAP_SYS_NICE capability", + priority); return; } - log_info("Set scheduling priority to %lu", rlim.rlim_cur); + log_info("Set real-time scheduling priority to %d", priority); } /** From e0c14f63c6b2691c2cf8adb2a8e83944f6531833 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 24 Jun 2023 02:59:04 +0100 Subject: [PATCH 102/177] core: don't use pthread functions Don't use pthread_{set,get}schedparam, which requires -lpthread. Use sched_setscheduler/sched_getparam instead, which is provided by libc. Signed-off-by: Yuxuan Shui --- src/picom.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index 9ddb49de6e..c07626b259 100644 --- a/src/picom.c +++ b/src/picom.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -2579,18 +2580,17 @@ static session_t *session_init(int argc, char **argv, Display *dpy, void set_rr_scheduling(void) { int priority = sched_get_priority_min(SCHED_RR); - int old_policy; int ret; struct sched_param param; - ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); + ret = sched_getparam(0, ¶m); if (ret != 0) { log_debug("Failed to get old scheduling priority"); return; } param.sched_priority = priority; - ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + ret = sched_setscheduler(0, SCHED_RR, ¶m); if (ret != 0) { log_info("Failed to set real-time scheduling priority to %d. Consider " "giving picom the CAP_SYS_NICE capability", From f8cdc81635a0405693beb7c802119acbbc426968 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 26 Jun 2023 13:25:53 +0100 Subject: [PATCH 103/177] core: add comment to set_rr_scheduling Signed-off-by: Yuxuan Shui --- src/picom.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/picom.c b/src/picom.c index c07626b259..126922e732 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2577,6 +2577,14 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(ps); return NULL; } + +/// Switch to real-time scheduling policy (SCHED_RR) if possible +/// +/// Make picom realtime to reduce latency, and make rendering times more predictable to +/// help pacing. +/// +/// This requires the user to set up permissions for the real-time scheduling. e.g. by +/// setting `ulimit -r`, or giving us the CAP_SYS_NICE capability. void set_rr_scheduling(void) { int priority = sched_get_priority_min(SCHED_RR); From d08b6092a3f59309e00467d567ee79582c53e5ca Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Mon, 26 Jun 2023 18:01:22 +0300 Subject: [PATCH 104/177] win: don't include GL/gl.h it seems unused and removing it addresses a fixme --- src/win.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/win.h b/src/win.h index fe733408b5..da46572b73 100644 --- a/src/win.h +++ b/src/win.h @@ -11,11 +11,6 @@ #include "uthash_extra.h" -// FIXME shouldn't need this -#ifdef CONFIG_OPENGL -#include -#endif - #include "c2.h" #include "compiler.h" #include "list.h" From 6496f753596e3779c15527947801eb40c9abd430 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 25 Jan 2023 02:18:36 +0300 Subject: [PATCH 105/177] update contributors list it's (almost) automatically generated from the git log and the contributors' emails are (trivially) obfuscated --- CONTRIBUTORS | 164 +++++++++++++++++++++++++++------------------------ 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9512bf83d2..d4373abd99 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,85 +1,95 @@ -Sorted in alphabetical order -Open an issue or pull request if you don't want your name listed here. +Sorted in alphabetical order. Feel free to open an issue or create a +pull request if you want to change or remove your mention. -Adam Jackson -adelin-b -Alexander Kapshuna -Antonin Décimo -Antonio Vivace -Avi-D-coder -Ben Friesen -Bernd Busse -Bert Gijsbers -bhagwan -Bodhi -Brottweiler -Carl Worth -Christopher Jeffrey -Corax26 -Dan Elkouby -Dana Jansens -Daniel Kwan -Dave Airlie +Adam Jackson +adelin-b +Alexander Kapshuna +Antonin Décimo +Antonio Vivace +Avi ד +Ben Friesen +Bernd Busse +Bert Gijsbers +bhagwan +Bodhi +Brottweiler +Carl Worth +Christopher Jeffrey +Corax26 +Dan Elkouby +Dana Jansens +Daniel Kwan +Dave Airlie David Schlachter dolio -Duncan -Dylan Araps -Einar Lielmanis -Eric Anholt +Duncan +Dylan Araps +Einar Lielmanis +Eric Anholt +Evgeniy Baskov Greg Flynn -Harish Rajagopal -hasufell -Ignacio Taranto +h7x4 +Harish Rajagopal +hasufell +i-c-u-p +Ignacio Taranto Istvan Petres -James Cloos -Jamey Sharp -Jan Beich -Jarrad -Javeed Shaikh -Jerónimo Navarro -jialeens -Johnny Pribyl -Keith Packard -Kevin Kelley -ktprograms -Lukas Schmelzeisen -mæp -Mark Tiefenbruck -Matthew Allum -Maxim Solovyov -Michael Reed -Michele Lambertucci -Namkhai Bourquin -Nate Hart -nia -notfoss -Omar Polo -orbea -@Paradigm0001 +Jake +James Cloos +Jamey Sharp +Jan Beich +Jarrad +Javeed Shaikh +Jerónimo Navarro +jialeens +Johnny Pribyl +Keith Packard +Kevin Kelley +ktprograms +Kurenshe Nurdaulet +Lukas Schmelzeisen +Mark Tiefenbruck +Matthew Allum +Maxim Solovyov +Michael Reed +Michele Lambertucci +mæp +Namkhai Bourquin +Nate Hart +nia +Nikolay Borodin +notfoss +Omar Polo +oofsauce +orbea +Paradigm0001 Patrick Collins -Peter Mattern -Phil Blundell -Que Quotion -Rafael Kitover -Richard Grenville -Rytis Karpuska -Samuel Hand -Scott Leggett -scrouthtv -Sebastien Waegeneire -Subhaditya Nath -Tasos Sahanidis -Thiago Kenji Okada -Tilman Sauerbeck -Tim van Dalen -Tomas Janousek -Tom Dörr +Peter Mattern +Phil Blundell +Que Quotion +Rafael Kitover +Richard Grenville +Rytis Karpuska +Samuel Hand +Scott Leggett +scrouthtv +Sebastien Waegeneire +Stefan Radziuk +Subhaditya Nath +Tasos Sahanidis +Thiago Kenji Okada +Tilman Sauerbeck +Tim Siegel +Tim van Dalen +tokyoneon78 +Tom Dörr +Tomas Janousek Toni Jarjour -Tuomas Kinnunen -Uli Schlachter -Walter Lapchynski -Will Dietz -XeCycle -Yuxuan Shui +Tuomas Kinnunen +Uli Schlachter +Walter Lapchynski +Will Dietz +XeCycle +Yuxuan Shui zilrich -ಠ_ಠ +ಠ_ಠ From d044e3e38663b16778695bb85732798770a361fa Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 28 Jun 2023 21:21:45 +0300 Subject: [PATCH 106/177] readme: simplify build instructions don't initialize submodules (we don't have any) and specify the source directory explicitly (assume that the current directory is the source one) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5b358bbe6b..f1f66b0989 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,7 @@ To build the documents, you need `asciidoc` ### To build ```bash -$ git submodule update --init --recursive -$ meson setup --buildtype=release . build +$ meson setup --buildtype=release build $ ninja -C build ``` @@ -75,12 +74,12 @@ If you have libraries and/or headers installed at non-default location (e.g. und You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: ```bash -$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release . build +$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release build ``` As an example, on FreeBSD, you might have to run meson with: ```bash -$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release . build +$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release build $ ninja -C build ``` From d4f72828f76ecacedfc2dcd0fb489f661be72bc8 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Wed, 28 Jun 2023 21:30:59 +0300 Subject: [PATCH 107/177] gitmodules: remove the .gitmodules file it's unused because we don't have any submodules --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 From 4e6dddc76e7db7f6c98b3dcb2efce192bae4e17d Mon Sep 17 00:00:00 2001 From: Monsterovich Date: Fri, 30 Jun 2023 00:28:45 +0200 Subject: [PATCH 108/177] win: don't re-bind mask image when there is already one Co-authored-by: Yuxuan Shui --- src/win.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/win.c b/src/win.c index a97733dbd4..1aa10fd217 100644 --- a/src/win.c +++ b/src/win.c @@ -368,7 +368,10 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color b->ops->shadow_from_mask == NULL) { w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); } else { - win_bind_mask(b, w); + if (!w->mask_image) { + // It's possible we already allocated a mask because of background blur + win_bind_mask(b, w); + } w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c); } if (!w->shadow_image) { From 1307d9ec709c9fbbe99939d46ad04c57d5e4b501 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 29 Jun 2023 05:39:36 +0100 Subject: [PATCH 109/177] core: isolate X connection with error handling into a struct Part of the long running effort to reduce the prevalence of `session_t`. After this, functions that communicate with X can make use of the error handling machinary (set_ignore_cookie, set_cant_fail_cookie) without needing to take a `session_t` parameter. This commit converts everything to use the new struct `x_connection`, most of the conversions are mechanical. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 8 +- src/backend/backend.h | 3 +- src/backend/backend_common.c | 71 +++--- src/backend/backend_common.h | 12 +- src/backend/dummy/dummy.c | 7 +- src/backend/gl/egl.c | 13 +- src/backend/gl/glx.c | 99 ++++---- src/backend/gl/glx.h | 3 +- src/backend/xrender/xrender.c | 184 +++++++-------- src/c2.c | 116 ++++++---- src/common.h | 79 +------ src/dbus.c | 2 +- src/event.c | 62 ++--- src/opengl.c | 33 +-- src/picom.c | 425 ++++++++++++++-------------------- src/picom.h | 2 +- src/render.c | 394 +++++++++++++++++-------------- src/render.h | 2 - src/vsync.c | 20 +- src/win.c | 112 ++++----- src/win.h | 4 +- src/x.c | 230 +++++++++++------- src/x.h | 200 ++++++++++++---- 23 files changed, 1095 insertions(+), 986 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index da1fa42996..f094555c33 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -91,10 +91,10 @@ void paint_all_new(session_t *ps, struct managed_win *t) { return handle_device_reset(ps); } if (ps->o.xrender_sync_fence) { - if (ps->xsync_exists && !x_fence_sync(ps->c, ps->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); + xcb_sync_destroy_fence(ps->c.c, ps->sync_fence); ps->sync_fence = XCB_NONE; ps->o.xrender_sync_fence = false; ps->xsync_exists = false; @@ -348,7 +348,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { } if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 && - w->randr_monitor < ps->randr_nmonitors) { + w->randr_monitor < ps->monitors.count) { // There can be a window where number of monitors is // updated, but the monitor number attached to the window // have not. @@ -358,7 +358,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { // bounds. pixman_region32_intersect( ®_shadow, ®_shadow, - &ps->randr_monitor_regs[w->randr_monitor]); + &ps->monitors.regions[w->randr_monitor]); } if (ps->o.transparent_clipping) { diff --git a/src/backend/backend.h b/src/backend/backend.h index 7cd64a082b..3ed7a761a8 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -23,8 +23,7 @@ struct backend_operations; typedef struct backend_base { struct backend_operations *ops; - xcb_connection_t *c; - xcb_window_t root; + struct x_connection *c; struct ev_loop *loop; /// Whether the backend can accept new render request at the moment diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index ecdefa4cf5..5b24c67a8c 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -19,17 +19,18 @@ /** * Generate a 1x1 Picture of a particular color. */ -xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool argb, - double a, double r, double g, double b) { +xcb_render_picture_t +solid_picture(struct x_connection *c, bool argb, double a, double r, double g, double b) { xcb_pixmap_t pixmap; xcb_render_picture_t picture; xcb_render_create_picture_value_list_t pa; xcb_render_color_t col; xcb_rectangle_t rect; - pixmap = x_create_pixmap(c, argb ? 32 : 8, d, 1, 1); - if (!pixmap) + pixmap = x_create_pixmap(c, argb ? 32 : 8, 1, 1); + if (!pixmap) { return XCB_NONE; + } pa.repeat = 1; picture = x_create_picture_with_standard_and_pixmap( @@ -37,7 +38,7 @@ xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool a XCB_RENDER_CP_REPEAT, &pa); if (!picture) { - xcb_free_pixmap(c, pixmap); + xcb_free_pixmap(c->c, pixmap); return XCB_NONE; } @@ -51,14 +52,14 @@ xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool a rect.width = 1; rect.height = 1; - xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); - xcb_free_pixmap(c, pixmap); + xcb_render_fill_rectangles(c->c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); + xcb_free_pixmap(c->c, pixmap); return picture; } -xcb_image_t * -make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) { +xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity, + int width, int height) { /* * We classify shadows into 4 kinds of regions * r = shadow radius @@ -84,8 +85,9 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, assert(d % 2 == 1); assert(d > 0); - ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight), - XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL); + ximage = + xcb_image_create_native(c->c, to_u16_checked(swidth), to_u16_checked(sheight), + XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL); if (!ximage) { log_error("failed to create an X image"); return 0; @@ -193,7 +195,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, /** * Generate shadow Picture for a window. */ -bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width, +bool build_shadow(struct x_connection *c, double opacity, const int width, const int height, const conv *kernel, xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) { xcb_image_t *shadow_image = NULL; @@ -207,9 +209,9 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i return false; } - shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height); + shadow_pixmap = x_create_pixmap(c, 8, shadow_image->width, shadow_image->height); shadow_pixmap_argb = - x_create_pixmap(c, 32, d, shadow_image->width, shadow_image->height); + x_create_pixmap(c, 32, shadow_image->width, shadow_image->height); if (!shadow_pixmap || !shadow_pixmap_argb) { log_error("Failed to create shadow pixmaps"); @@ -225,11 +227,11 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i } gc = x_new_id(c); - xcb_create_gc(c, gc, shadow_pixmap, 0, NULL); + xcb_create_gc(c->c, gc, shadow_pixmap, 0, NULL); // We need to make room for protocol metadata in the request. The metadata should // be 24 bytes plus padding, let's be generous and give it 1kb - auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024; + auto maximum_image_size = xcb_get_maximum_request_length(c->c) * 4 - 1024; auto maximum_row = to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX)); if (maximum_row <= 0) { @@ -248,23 +250,23 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i } uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data); - xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc, + xcb_put_image(c->c, (uint8_t)shadow_image->format, shadow_pixmap, gc, shadow_image->width, batch_height, 0, to_i16_checked(row), 0, shadow_image->depth, shadow_image->stride * batch_height, shadow_image->data + offset); } - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture, + xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture, shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width, shadow_image->height); *pixmap = shadow_pixmap_argb; *pict = shadow_picture_argb; - xcb_free_gc(c, gc); + xcb_free_gc(c->c, gc); xcb_image_destroy(shadow_image); - xcb_free_pixmap(c, shadow_pixmap); - xcb_render_free_picture(c, shadow_picture); + xcb_free_pixmap(c->c, shadow_pixmap); + x_free_picture(c, shadow_picture); return true; @@ -273,19 +275,19 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i xcb_image_destroy(shadow_image); } if (shadow_pixmap) { - xcb_free_pixmap(c, shadow_pixmap); + xcb_free_pixmap(c->c, shadow_pixmap); } if (shadow_pixmap_argb) { - xcb_free_pixmap(c, shadow_pixmap_argb); + xcb_free_pixmap(c->c, shadow_pixmap_argb); } if (shadow_picture) { - xcb_render_free_picture(c, shadow_picture); + x_free_picture(c, shadow_picture); } if (shadow_picture_argb) { - xcb_render_free_picture(c, shadow_picture_argb); + x_free_picture(c, shadow_picture_argb); } if (gc) { - xcb_free_gc(c, gc); + xcb_free_gc(c->c, gc); } return false; @@ -294,22 +296,22 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i void *default_backend_render_shadow(backend_t *backend_data, int width, int height, struct backend_shadow_context *sctx, struct color color) { const conv *kernel = (void *)sctx; - xcb_render_picture_t shadow_pixel = solid_picture( - backend_data->c, backend_data->root, true, 1, color.red, color.green, color.blue); + xcb_render_picture_t shadow_pixel = + solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue); xcb_pixmap_t shadow = XCB_NONE; xcb_render_picture_t pict = XCB_NONE; - if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height, - kernel, shadow_pixel, &shadow, &pict)) { - xcb_render_free_picture(backend_data->c, shadow_pixel); + if (!build_shadow(backend_data->c, color.alpha, width, height, kernel, + shadow_pixel, &shadow, &pict)) { + x_free_picture(backend_data->c, shadow_pixel); return NULL; } auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32); void *ret = backend_data->ops->bind_pixmap( backend_data, shadow, x_get_visual_info(backend_data->c, visual), true); - xcb_render_free_picture(backend_data->c, pict); - xcb_render_free_picture(backend_data->c, shadow_pixel); + x_free_picture(backend_data->c, pict); + x_free_picture(backend_data->c, shadow_pixel); return ret; } @@ -506,9 +508,8 @@ struct backend_image *default_new_backend_image(int w, int h) { } void init_backend_base(struct backend_base *base, session_t *ps) { - base->c = ps->c; + base->c = &ps->c; base->loop = ps->loop; - base->root = ps->root; base->busy = false; base->ops = NULL; } diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index c72a1686b3..2e49cecf71 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -44,15 +44,15 @@ struct backend_image { int border_width; }; -bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, - int height, const conv *kernel, xcb_render_picture_t shadow_pixel, +bool build_shadow(struct x_connection *, double opacity, int width, int height, + const conv *kernel, xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); -xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb, - double a, double r, double g, double b); +xcb_render_picture_t +solid_picture(struct x_connection *, bool argb, double a, double r, double g, double b); -xcb_image_t * -make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height); +xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity, + int width, int height); /// The default implementation of `is_win_transparent`, it simply looks at win::mode. So /// this is not suitable for backends that alter the content of windows diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 5f5e1229a7..4e3d713873 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -30,9 +30,8 @@ struct dummy_data { struct backend_base *dummy_init(struct session *ps attr_unused) { auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); - ret->c = ps->c; + ret->c = &ps->c; ret->loop = ps->loop; - ret->root = ps->root; ret->busy = false; return ret; } @@ -44,7 +43,7 @@ void dummy_deinit(struct backend_base *data) { HASH_DEL(dummy->images, img); free(img->refcount); if (img->owned) { - xcb_free_pixmap(data->c, img->pixmap); + xcb_free_pixmap(data->c->c, img->pixmap); } free(img); } @@ -118,7 +117,7 @@ void dummy_release_image(backend_t *base, void *image) { HASH_DEL(dummy->images, img); free(img->refcount); if (img->owned) { - xcb_free_pixmap(base->c, img->pixmap); + xcb_free_pixmap(base->c->c, img->pixmap); } free(img); } diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 761eb63504..a78c1903da 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -79,7 +79,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) { } if (p->owned) { - xcb_free_pixmap(base->c, p->pixmap); + xcb_free_pixmap(base->c->c, p->pixmap); p->pixmap = XCB_NONE; } @@ -154,10 +154,10 @@ static backend_t *egl_init(session_t *ps) { } gd = ccalloc(1, struct egl_data); - gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy, + gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->c.dpy, (EGLAttrib[]){ EGL_PLATFORM_X11_SCREEN_EXT, - ps->scr, + ps->c.screen, EGL_NONE, }); if (gd->display == EGL_NO_DISPLAY) { @@ -190,7 +190,7 @@ static backend_t *egl_init(session_t *ps) { goto end; } - auto visual_info = x_get_visual_info(ps->c, ps->vis); + auto visual_info = x_get_visual_info(&ps->c, ps->c.screen_info->root_visual); EGLConfig config = NULL; int nconfigs = 1; // clang-format off @@ -280,7 +280,8 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b struct egl_data *gd = (void *)base; struct egl_pixmap *eglpixmap = NULL; - auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); + auto r = + xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), NULL); if (!r) { log_error("Invalid pixmap %#010x", pixmap); return NULL; @@ -335,7 +336,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b free(eglpixmap); if (owned) { - xcb_free_pixmap(base->c, pixmap); + xcb_free_pixmap(base->c->c, pixmap); } free(wd); return NULL; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 80056c6798..d30dc9f291 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -42,8 +42,6 @@ struct _glx_pixmap { struct _glx_data { struct gl_data gl; - Display *display; - int screen; xcb_window_t target_win; GLXContext ctx; }; @@ -52,18 +50,18 @@ struct _glx_data { do { \ if (glXGetFBConfigAttrib(a, b, attr, c)) { \ log_info("Cannot get FBConfig attribute " #attr); \ - continue; \ + break; \ } \ } while (0) -struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvisual_info m) { +struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *c, struct xvisual_info m) { log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size, m.blue_size, m.green_size, m.alpha_size, m.visual_depth); int ncfg; // clang-format off GLXFBConfig *cfg = - glXChooseFBConfig(dpy, screen, (int[]){ + glXChooseFBConfig(c->dpy, c->screen, (int[]){ GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, @@ -87,25 +85,26 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi GLXFBConfig ret; for (int i = 0; i < ncfg; i++) { int depthbuf, stencil, doublebuf, bufsize; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_STENCIL_SIZE, &stencil); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_STENCIL_SIZE, &stencil); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf); if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) { continue; } int red, green, blue; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_RED_SIZE, &red); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BLUE_SIZE, &blue); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_GREEN_SIZE, &green); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_RED_SIZE, &red); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BLUE_SIZE, &blue); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_GREEN_SIZE, &green); if (red != m.red_size || green != m.green_size || blue != m.blue_size) { // Color size doesn't match, this cannot work continue; } int rgb, rgba; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, + &rgba); if (!rgb && !rgba) { log_info("FBConfig is neither RGBA nor RGB, we cannot " "handle this setup."); @@ -113,10 +112,9 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi } int visual; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_VISUAL_ID, &visual); if (m.visual_depth != -1 && - x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) != - m.visual_depth) { + x_get_visual_depth(c, (xcb_visualid_t)visual) != m.visual_depth) { // FBConfig and the correspondent X Visual might not have the same // depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is // quite common, seen in both open source and proprietary drivers. @@ -129,9 +127,9 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi // All check passed, we are using this one. found = true; ret = cfg[i]; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, - &texture_tgts); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted); + glXGetFBConfigAttribChecked( + c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_tgts); + glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted); // Prefer the texture format with matching alpha, with the other one as // fallback @@ -161,24 +159,22 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi * Free a glx_texture_t. */ static void glx_release_image(backend_t *base, struct gl_texture *tex) { - struct _glx_data *gd = (void *)base; - struct _glx_pixmap *p = tex->user_data; // Release binding if (p->glpixmap && tex->texture) { glBindTexture(GL_TEXTURE_2D, tex->texture); - glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT); + glXReleaseTexImageEXT(base->c->dpy, p->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(GL_TEXTURE_2D, 0); } // Free GLX Pixmap if (p->glpixmap) { - glXDestroyPixmap(gd->display, p->glpixmap); + glXDestroyPixmap(base->c->dpy, p->glpixmap); p->glpixmap = 0; } if (p->owned) { - xcb_free_pixmap(base->c, p->pixmap); + xcb_free_pixmap(base->c->c, p->pixmap); p->pixmap = XCB_NONE; } @@ -196,8 +192,8 @@ void glx_deinit(backend_t *base) { // Destroy GLX context if (gd->ctx) { - glXMakeCurrent(gd->display, None, NULL); - glXDestroyContext(gd->display, gd->ctx); + glXMakeCurrent(base->c->dpy, None, NULL); + glXDestroyContext(base->c->dpy, gd->ctx); gd->ctx = 0; } @@ -233,12 +229,10 @@ static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawab */ static backend_t *glx_init(session_t *ps) { bool success = false; - glxext_init(ps->dpy, ps->scr); + glxext_init(ps->c.dpy, ps->c.screen); auto gd = ccalloc(1, struct _glx_data); init_backend_base(&gd->gl.base, ps); - gd->display = ps->dpy; - gd->screen = ps->scr; gd->target_win = session_get_target_window(ps); XVisualInfo *pvis = NULL; @@ -251,8 +245,8 @@ static backend_t *glx_init(session_t *ps) { // Get XVisualInfo int nitems = 0; - XVisualInfo vreq = {.visualid = ps->vis}; - pvis = XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems); + XVisualInfo vreq = {.visualid = ps->c.screen_info->root_visual}; + pvis = XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems); if (!pvis) { log_error("Failed to acquire XVisualInfo for current visual."); goto end; @@ -260,22 +254,22 @@ static backend_t *glx_init(session_t *ps) { // Ensure the visual is double-buffered int value = 0; - if (glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) { + if (glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) { log_error("Root visual is not a GL visual."); goto end; } - if (glXGetConfig(ps->dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) { + if (glXGetConfig(ps->c.dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) { log_error("Root visual lacks stencil buffer."); goto end; } - if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { + if (glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { log_error("Root visual is not a double buffered GL visual."); goto end; } - if (glXGetConfig(ps->dpy, pvis, GLX_RGBA, &value) || !value) { + if (glXGetConfig(ps->c.dpy, pvis, GLX_RGBA, &value) || !value) { log_error("Root visual is a color index visual, not supported"); goto end; } @@ -293,11 +287,11 @@ static backend_t *glx_init(session_t *ps) { // Find a fbconfig with visualid matching the one from the target win, so we can // be sure that the fbconfig is compatible with our target window. int ncfgs; - GLXFBConfig *cfg = glXGetFBConfigs(gd->display, gd->screen, &ncfgs); + GLXFBConfig *cfg = glXGetFBConfigs(ps->c.dpy, ps->c.screen, &ncfgs); bool found = false; for (int i = 0; i < ncfgs; i++) { int visualid; - glXGetFBConfigAttribChecked(gd->display, cfg[i], GLX_VISUAL_ID, &visualid); + glXGetFBConfigAttribChecked(ps->c.dpy, cfg[i], GLX_VISUAL_ID, &visualid); if ((VisualID)visualid != pvis->visualid) { continue; } @@ -316,7 +310,7 @@ static backend_t *glx_init(session_t *ps) { attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB; } - gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, attributes); + gd->ctx = glXCreateContextAttribsARB(ps->c.dpy, cfg[i], 0, true, attributes); free(cfg); if (!gd->ctx) { @@ -334,7 +328,7 @@ static backend_t *glx_init(session_t *ps) { // Attach GLX context GLXDrawable tgt = gd->target_win; - if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) { + if (!glXMakeCurrent(ps->c.dpy, tgt, gd->ctx)) { log_error("Failed to attach GLX context."); goto end; } @@ -348,11 +342,11 @@ static backend_t *glx_init(session_t *ps) { gd->gl.release_user_data = glx_release_image; if (ps->o.vsync) { - if (!glx_set_swap_interval(1, ps->dpy, tgt)) { + if (!glx_set_swap_interval(1, ps->c.dpy, tgt)) { log_error("Failed to enable vsync."); } } else { - glx_set_swap_interval(0, ps->dpy, tgt); + glx_set_swap_interval(0, ps->c.dpy, tgt); } success = true; @@ -372,7 +366,6 @@ static backend_t *glx_init(session_t *ps) { static void * glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { - struct _glx_data *gd = (void *)base; struct _glx_pixmap *glxpixmap = NULL; // Retrieve pixmap parameters, if they aren't provided if (fmt.visual_depth > OPENGL_MAX_DEPTH) { @@ -386,7 +379,8 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b return false; } - auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); + auto r = + xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), NULL); if (!r) { log_error("Invalid pixmap %#010x", pixmap); return NULL; @@ -400,7 +394,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b wd->inner = (struct backend_image_inner_base *)inner; free(r); - auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt); + auto fbcfg = glx_find_fbconfig(base->c, fmt); if (!fbcfg) { log_error("Couldn't find FBConfig with requested visual %x", fmt.visual); goto err; @@ -429,7 +423,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b glxpixmap = cmalloc(struct _glx_pixmap); glxpixmap->pixmap = pixmap; - glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs); + glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbcfg->cfg, pixmap, attrs); glxpixmap->owned = owned; free(fbcfg); @@ -446,19 +440,19 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b inner->has_alpha = fmt.alpha_size != 0; wd->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, inner->texture); - glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); + glXBindTexImageEXT(base->c->dpy, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); return wd; err: if (glxpixmap && glxpixmap->glpixmap) { - glXDestroyPixmap(gd->display, glxpixmap->glpixmap); + glXDestroyPixmap(base->c->dpy, glxpixmap->glpixmap); } free(glxpixmap); if (owned) { - xcb_free_pixmap(base->c, pixmap); + xcb_free_pixmap(base->c->c, pixmap); } free(wd); return NULL; @@ -467,7 +461,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b static void glx_present(backend_t *base, const region_t *region attr_unused) { struct _glx_data *gd = (void *)base; gl_present(base, region); - glXSwapBuffers(gd->display, gd->target_win); + glXSwapBuffers(base->c->dpy, gd->target_win); } static int glx_buffer_age(backend_t *base) { @@ -477,15 +471,14 @@ static int glx_buffer_age(backend_t *base) { struct _glx_data *gd = (void *)base; unsigned int val; - glXQueryDrawable(gd->display, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val); + glXQueryDrawable(base->c->dpy, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val); return (int)val ?: -1; } static void glx_diagnostics(backend_t *base) { - struct _glx_data *gd = (void *)base; bool warn_software_rendering = false; const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"}; - auto glx_vendor = glXGetClientString(gd->display, GLX_VENDOR); + auto glx_vendor = glXGetClientString(base->c->dpy, GLX_VENDOR); printf("* Driver vendors:\n"); printf(" * GLX: %s\n", glx_vendor); printf(" * GL: %s\n", glGetString(GL_VENDOR)); diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h index 44b4da0595..ce8702a974 100644 --- a/src/backend/gl/glx.h +++ b/src/backend/gl/glx.h @@ -41,8 +41,7 @@ struct glx_fbconfig_criteria { int visual_depth; }; -struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info); - +struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *, struct xvisual_info); struct glxext_info { bool initialized; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 0041817aca..bb2acd456a 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -28,7 +28,6 @@ typedef struct _xrender_data { backend_t base; /// If vsync is enabled and supported by the current system bool vsync; - xcb_visualid_t default_visual; /// Target window xcb_window_t target_win; /// Painting target, it is either the root or the overlay @@ -104,9 +103,9 @@ struct xrender_image { /// Make a picture of size width x height, which has a rounded rectangle of corner_radius /// rendered in it. struct xrender_rounded_rectangle_cache * -make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src, - xcb_drawable_t root, int width, int height, int corner_radius) { - auto picture = x_create_picture_with_standard(c, root, width, height, +make_rounded_corner_cache(struct x_connection *c, xcb_render_picture_t src, int width, + int height, int corner_radius) { + auto picture = x_create_picture_with_standard(c, width, height, XCB_PICT_STANDARD_ARGB_32, 0, NULL); if (picture == XCB_NONE) { return NULL; @@ -160,7 +159,7 @@ make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src, } #undef ADD_POINT - XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture, + XCB_AWAIT_VOID(xcb_render_tri_strip, c->c, XCB_RENDER_PICT_OP_SRC, src, picture, x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0, (uint32_t)point_count, points); free(points); @@ -182,30 +181,29 @@ static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrende *allocated = true; x_clear_picture_clip_region(xd->base.c, inner->pict); auto ret = x_create_picture_with_visual( - xd->base.c, xd->base.root, inner->width, inner->height, inner->visual, - XCB_RENDER_CP_REPEAT, + xd->base.c, inner->width, inner->height, inner->visual, XCB_RENDER_CP_REPEAT, (xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD}); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); // Remember: the mask has a 1-pixel border if (mask->base.corner_radius != 0) { if (mask->rounded_rectangle == NULL) { mask->rounded_rectangle = make_rounded_corner_cache( - xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2, + xd->base.c, xd->white_pixel, inner->width - 2, inner->height - 2, (int)mask->base.corner_radius); } - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE, mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0, 0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2)); } if (mask->base.color_inverted) { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel, XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); } if (alpha_pict != XCB_NONE) { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict, ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), to_u16_checked(inner->height)); } @@ -246,43 +244,43 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) { - xrimg->rounded_rectangle = make_rounded_corner_cache( - xd->base.c, xd->white_pixel, xd->base.root, inner->width, - inner->height, (int)img->corner_radius); + xrimg->rounded_rectangle = + make_rounded_corner_cache(xd->base.c, xd->white_pixel, inner->width, + inner->height, (int)img->corner_radius); } if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) { // Apply image properties using a temporary image, because the source // image is transparent. Otherwise the properties can be applied directly // on the target image. - auto tmp_pict = - x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width, - inner->height, inner->visual, 0, NULL); + auto tmp_pict = x_create_picture_with_visual( + xd->base.c, inner->width, inner->height, inner->visual, 0, NULL); // Set clip region translated to source coordinate x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x), to_i16_checked(-dst.y), ®); // Copy source -> tmp - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( - xd->base.c, xd->base.root, tmpw, tmph, inner->visual, - 0, NULL); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, + xd->base.c, tmpw, tmph, inner->visual, 0, NULL); + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, tmp_pict, XCB_NONE, tmp_pict2, 0, 0, 0, 0, 0, 0, tmpw, tmph); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, + xcb_render_composite(xd->base.c->c, + XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); xcb_render_composite( - xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2, + xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - xcb_render_free_picture(xd->base.c, tmp_pict2); + x_free_picture(xd->base.c, tmp_pict2); } else { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, + xcb_render_composite(xd->base.c->c, + XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); } @@ -297,33 +295,34 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, .height = tmph, }; - xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER, + xcb_render_fill_rectangles(xd->base.c->c, XCB_RENDER_PICT_OP_OVER, tmp_pict, dim_color, 1, &rect); } if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { // Clip tmp_pict with a rounded rectangle - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE, xrimg->rounded_rectangle->p, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); } - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, + xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_OVER, tmp_pict, mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, tmpeh); - xcb_render_free_picture(xd->base.c, tmp_pict); + xcb_render_free_picture(xd->base.c->c, tmp_pict); } else { uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); - xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0, - mask_dst_x, mask_dst_y, to_i16_checked(dst.x), + xcb_render_composite(xd->base.c->c, op, inner->pict, mask_pict, result, 0, + 0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, tmpeh); if (img->dim != 0 || img->color_inverted) { // Apply properties, if we reach here, then has_alpha == false assert(!has_alpha); if (img->color_inverted) { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, + xcb_render_composite(xd->base.c->c, + XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, result, 0, 0, 0, 0, to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, tmpeh); @@ -338,13 +337,14 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, .height = tmpeh, }; - xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER, + xcb_render_fill_rectangles(xd->base.c->c, + XCB_RENDER_PICT_OP_OVER, result, dim_color, 1, &rect); } } } if (mask_allocated) { - xcb_render_free_picture(xd->base.c, mask_pict); + x_free_picture(xd->base.c, mask_pict); } pixman_region32_fini(®); } @@ -362,7 +362,7 @@ static void fill(backend_t *base, struct color c, const region_t *clip) { x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip); // color is in X fixed point representation xcb_render_fill_rectangles( - base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2], + base->c->c, XCB_RENDER_PICT_OP_OVER, xd->back[2], (xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff), .green = (uint16_t)(c.green * 0xffff), .blue = (uint16_t)(c.blue * 0xffff), @@ -382,7 +382,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask } struct _xrender_data *xd = (void *)backend_data; - xcb_connection_t *c = xd->base.c; + auto c = xd->base.c; region_t reg_op; pixman_region32_init(®_op); pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible); @@ -405,10 +405,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD}; xcb_render_picture_t tmp_picture[2] = { - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, - xd->default_visual, pic_attrs_mask, &pic_attrs), - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, - xd->default_visual, pic_attrs_mask, &pic_attrs)}; + x_create_picture_with_visual(xd->base.c, width_resized, height_resized, + xd->base.c->screen_info->root_visual, + pic_attrs_mask, &pic_attrs), + x_create_picture_with_visual(xd->base.c, width_resized, height_resized, + xd->base.c->screen_info->root_visual, + pic_attrs_mask, &pic_attrs)}; if (!tmp_picture[0] || !tmp_picture[1]) { log_error("Failed to build intermediate Picture."); @@ -445,8 +447,8 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask // Copy from source picture to destination. The filter must // be applied on source picture, to get the nearby pixels outside the // window. - xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)), - filter, + xcb_render_set_picture_filter(c->c, src_pict, + to_u16_checked(strlen(filter)), filter, to_u32_checked(bctx->x_blur_kernel[i]->size), bctx->x_blur_kernel[i]->kernel); @@ -454,21 +456,21 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask // First pass, back buffer -> tmp picture // (we do this even if this is also the last pass, because we // cannot do back buffer -> back buffer) - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, dst_pict, to_i16_checked(extent_resized->x1), to_i16_checked(extent_resized->y1), 0, 0, 0, 0, width_resized, height_resized); } else if (i < bctx->x_blur_kernel_count - 1) { // This is not the last pass or the first pass, // tmp picture 1 -> tmp picture 2 - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, + xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0, width_resized, height_resized); } else { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); // This is the last pass, and we are doing more than 1 pass xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], + c->c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1), to_i16_checked(extent_resized->y1 - mask_dst.y + 1), to_i16_checked(extent_resized->x1), @@ -477,7 +479,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask // reset filter xcb_render_set_picture_filter( - c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL); + c->c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL); src_pict = tmp_picture[current]; dst_pict = tmp_picture[!current]; @@ -488,15 +490,15 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask if (i == 1) { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, + c->c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1), to_i16_checked(extent_resized->y1 - mask_dst.y + 1), to_i16_checked(extent_resized->x1), to_i16_checked(extent_resized->y1), width_resized, height_resized); } - xcb_render_free_picture(c, tmp_picture[0]); - xcb_render_free_picture(c, tmp_picture[1]); + x_free_picture(c, tmp_picture[0]); + x_free_picture(c, tmp_picture[1]); pixman_region32_fini(®_op); pixman_region32_fini(®_op_resized); return true; @@ -505,7 +507,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask static void * bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { xcb_generic_error_t *e; - auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e); + auto r = xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), &e); if (!r) { log_error("Invalid pixmap: %#010x", pixmap); x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code); @@ -540,9 +542,9 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool return img; } static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) { - xcb_render_free_picture(base->c, inner->pict); + x_free_picture(base->c, inner->pict); if (inner->owned) { - xcb_free_pixmap(base->c, inner->pixmap); + xcb_free_pixmap(base->c->c, inner->pixmap); } free(inner); } @@ -556,7 +558,7 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c assert(cache->refcount > 0); cache->refcount--; if (cache->refcount == 0) { - xcb_render_free_picture(base->c, cache->p); + x_free_picture(base->c, cache->p); free(cache); } } @@ -575,22 +577,22 @@ static void release_image(backend_t *base, void *image) { static void deinit(backend_t *backend_data) { struct _xrender_data *xd = (void *)backend_data; for (int i = 0; i < 256; i++) { - xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]); + x_free_picture(xd->base.c, xd->alpha_pict[i]); } - xcb_render_free_picture(xd->base.c, xd->target); + x_free_picture(xd->base.c, xd->target); for (int i = 0; i < 3; i++) { if (xd->back[i] != XCB_NONE) { - xcb_render_free_picture(xd->base.c, xd->back[i]); + x_free_picture(xd->base.c, xd->back[i]); } if (xd->back_pixmap[i] != XCB_NONE) { - xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); + xcb_free_pixmap(xd->base.c->c, xd->back_pixmap[i]); } } if (xd->present_event) { - xcb_unregister_for_special_event(xd->base.c, xd->present_event); + xcb_unregister_for_special_event(xd->base.c->c, xd->present_event); } - xcb_render_free_picture(xd->base.c, xd->white_pixel); - xcb_render_free_picture(xd->base.c, xd->black_pixel); + x_free_picture(xd->base.c, xd->white_pixel); + x_free_picture(xd->base.c, xd->black_pixel); free(xd); } @@ -609,7 +611,7 @@ static void present(backend_t *base, const region_t *region) { x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); // Update the back buffer first, then present - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], + xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, 0, orig_x, orig_y, region_width, region_height); @@ -618,10 +620,10 @@ static void present(backend_t *base, const region_t *region) { // Make sure we got reply from PresentPixmap before waiting for events, // to avoid deadlock auto e = xcb_request_check( - base->c, xcb_present_pixmap_checked( - xd->base.c, xd->target_win, - xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0, - 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL)); + base->c->c, xcb_present_pixmap_checked( + xd->base.c->c, xd->target_win, + xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0, + 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL)); x_destroy_region(base->c, xregion); if (e) { log_error("Failed to present pixmap"); @@ -630,7 +632,7 @@ static void present(backend_t *base, const region_t *region) { } // TODO(yshui) don't block wait for present completion xcb_present_generic_event_t *pev = - (void *)xcb_wait_for_special_event(base->c, xd->present_event); + (void *)xcb_wait_for_special_event(base->c->c, xd->present_event); if (!pev) { // We don't know what happened, maybe X died // But reset buffer age, so in case we do recover, we will @@ -654,7 +656,7 @@ static void present(backend_t *base, const region_t *region) { free(pev); } else { // No vsync needed, draw into the target picture directly - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], + xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x, orig_y, region_width, region_height); } @@ -673,7 +675,7 @@ static int buffer_age(backend_t *backend_data) { static struct _xrender_image_data_inner * new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) { auto new_inner = ccalloc(1, struct _xrender_image_data_inner); - new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h); + new_inner->pixmap = x_create_pixmap(base->c, depth, w, h); if (new_inner->pixmap == XCB_NONE) { log_error("Failed to create pixmap for copy"); free(new_inner); @@ -683,7 +685,7 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) { base->c, visual, new_inner->pixmap, 0, NULL); if (new_inner->pict == XCB_NONE) { log_error("Failed to create picture for copy"); - xcb_free_pixmap(base->c, new_inner->pixmap); + xcb_free_pixmap(base->c->c, new_inner->pixmap); free(new_inner); return NULL; } @@ -705,12 +707,12 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) { auto inner = new_inner(base, size.width + 2, size.height + 2, x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32); - xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT, + xcb_render_change_picture(base->c->c, inner->pict, XCB_RENDER_CP_REPEAT, (uint32_t[]){XCB_RENDER_REPEAT_PAD}); const rect_t *extent = pixman_region32_extents((region_t *)reg); x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg); xcb_render_fill_rectangles( - base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1, (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1), .y = to_i16_checked(extent->y1 + 1), @@ -720,7 +722,7 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) { // Paint the border transparent xcb_render_fill_rectangles( - base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4, (xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1}, {.x = 0, .y = 0, .width = 1, .height = h16}, @@ -758,7 +760,7 @@ static bool decouple_image(backend_t *base, struct backend_image *img, const reg } x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg); - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, + xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), to_u16_checked(inner->height)); @@ -797,8 +799,8 @@ static bool image_op(backend_t *base, enum image_operations op, void *image, auto inner = (struct _xrender_image_data_inner *)img->inner; auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)]; x_set_picture_clip_region(base->c, inner->pict, 0, 0, ®); - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict, - XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0, + xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_OUT_REVERSE, + alpha_pict, XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), to_u16_checked(inner->height)); inner->has_alpha = true; @@ -877,24 +879,24 @@ static backend_t *backend_xrender_init(session_t *ps) { for (int i = 0; i <= MAX_ALPHA; ++i) { double o = (double)i / (double)MAX_ALPHA; - xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0); + xd->alpha_pict[i] = solid_picture(&ps->c, false, o, 0, 0, 0); assert(xd->alpha_pict[i] != XCB_NONE); } xd->target_width = ps->root_width; xd->target_height = ps->root_height; - xd->default_visual = ps->vis; - xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0); - xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1); + xd->black_pixel = solid_picture(&ps->c, true, 1, 0, 0, 0); + xd->white_pixel = solid_picture(&ps->c, true, 1, 1, 1, 1); xd->target_win = session_get_target_window(ps); xcb_render_create_picture_value_list_t pa = { .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, }; xd->target = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + &ps->c, ps->c.screen_info->root_visual, xd->target_win, + XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); + auto pictfmt = x_get_pictform_for_visual(&ps->c, ps->c.screen_info->root_visual); if (!pictfmt) { log_fatal("Default visual is invalid"); abort(); @@ -902,11 +904,11 @@ static backend_t *backend_xrender_init(session_t *ps) { xd->vsync = ps->o.vsync; if (ps->present_exists) { - auto eid = x_new_id(ps->c); + auto eid = x_new_id(&ps->c); auto e = - xcb_request_check(ps->c, xcb_present_select_input_checked( - ps->c, eid, xd->target_win, - XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY)); + xcb_request_check(ps->c.c, xcb_present_select_input_checked( + ps->c.c, eid, xd->target_win, + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY)); if (e) { log_error("Cannot select present input, vsync will be disabled"); xd->vsync = false; @@ -914,7 +916,7 @@ static backend_t *backend_xrender_init(session_t *ps) { } xd->present_event = - xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL); + xcb_register_for_special_xge(ps->c.c, &xcb_present_id, eid, NULL); if (!xd->present_event) { log_error("Cannot register for special XGE, vsync will be " "disabled"); @@ -928,14 +930,14 @@ static backend_t *backend_xrender_init(session_t *ps) { // double buffering. int first_buffer_index = xd->vsync ? 0 : 2; for (int i = first_buffer_index; i < 3; i++) { - xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root, - to_u16_checked(ps->root_width), - to_u16_checked(ps->root_height)); + xd->back_pixmap[i] = + x_create_pixmap(&ps->c, pictfmt->depth, to_u16_checked(ps->root_width), + to_u16_checked(ps->root_height)); const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; const xcb_render_create_picture_value_list_t pic_attrs = { .repeat = XCB_RENDER_REPEAT_PAD}; xd->back[i] = x_create_picture_with_pictfmt_and_pixmap( - ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs); + &ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs); xd->buffer_age[i] = -1; if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) { log_error("Cannot create pixmap for rendering"); diff --git a/src/c2.c b/src/c2.c index f8af6992ed..823d30ea7c 100644 --- a/src/c2.c +++ b/src/c2.c @@ -336,17 +336,19 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p * Parse a condition string. */ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { - if (!pattern) + if (!pattern) { return NULL; + } // Parse the pattern c2_ptr_t result = C2_PTR_INIT; int offset = -1; - if (strlen(pattern) >= 2 && ':' == pattern[1]) + if (strlen(pattern) >= 2 && ':' == pattern[1]) { offset = c2_parse_legacy(pattern, 0, &result); - else + } else { offset = c2_parse_grp(pattern, 0, &result, 0); + } if (offset < 0) { c2_freep(&result); @@ -395,11 +397,13 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) { */ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) { // Check for recursion levels - if (level > C2_MAX_LEVELS) + if (level > C2_MAX_LEVELS) { c2_error("Exceeded maximum recursion levels."); + } - if (!pattern) + if (!pattern) { return -1; + } // Expected end character const char endchar = (offset ? ')' : '\0'); @@ -428,17 +432,20 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int assert(elei <= 2); // Jump over spaces - if (isspace((unsigned char)pattern[offset])) + if (isspace((unsigned char)pattern[offset])) { continue; + } // Handle end of group - if (')' == pattern[offset]) + if (')' == pattern[offset]) { break; + } // Handle "!" if ('!' == pattern[offset]) { - if (!next_expected) + if (!next_expected) { c2_error("Unexpected \"!\"."); + } neg = !neg; continue; @@ -446,8 +453,9 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // Handle AND and OR if ('&' == pattern[offset] || '|' == pattern[offset]) { - if (next_expected) + if (next_expected) { c2_error("Unexpected logical operator."); + } next_expected = true; if (!mstrncmp("&&", pattern + offset)) { @@ -456,15 +464,17 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int } else if (!mstrncmp("||", pattern + offset)) { ops[elei] = C2_B_OOR; ++offset; - } else + } else { c2_error("Illegal logical operator."); + } continue; } // Parsing an element - if (!next_expected) + if (!next_expected) { c2_error("Unexpected expression."); + } assert(!elei || ops[elei]); @@ -491,21 +501,25 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // It's a subgroup if it starts with '(' if ('(' == pattern[offset]) { - if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) + if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) { goto fail; + } } // Otherwise it's a leaf else { - if ((offset = c2_parse_target(pattern, offset, pele)) < 0) + if ((offset = c2_parse_target(pattern, offset, pele)) < 0) { goto fail; + } assert(!pele->isbranch && !c2_ptr_isempty(*pele)); - if ((offset = c2_parse_op(pattern, offset, pele)) < 0) + if ((offset = c2_parse_op(pattern, offset, pele)) < 0) { goto fail; + } - if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) + if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) { goto fail; + } } // Decrement offset -- we will increment it in loop update --offset; @@ -513,10 +527,11 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int // Apply negation if (neg) { neg = false; - if (pele->isbranch) + if (pele->isbranch) { pele->b->neg = !pele->b->neg; - else + } else { pele->l->neg = !pele->l->neg; + } } next_expected = false; @@ -525,10 +540,12 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int } // Wrong end character? - if (pattern[offset] && !endchar) + if (pattern[offset] && !endchar) { c2_error("Expected end of string but found '%c'.", pattern[offset]); - if (!pattern[offset] && endchar) + } + if (!pattern[offset] && endchar) { c2_error("Expected '%c' but found end of string.", endchar); + } // Handle end of group if (!elei) { @@ -544,8 +561,9 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int *presult = eles[0]; - if (')' == pattern[offset]) + if (')' == pattern[offset]) { ++offset; + } return offset; @@ -778,11 +796,11 @@ static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) { // Parse operator while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) { - if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) + if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) { pleaf->op = C2_L_OGTEQ; - else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) + } else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) { pleaf->op = C2_L_OLTEQ; - else if (pleaf->op) { + } else if (pleaf->op) { c2_error("Duplicate operator."); } else { switch (pattern[offset]) { @@ -797,9 +815,10 @@ static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) { } // Check for problems - if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) + if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) { c2_error("Exists/greater-than/less-than operators cannot have a " "qualifier."); + } return offset; @@ -891,9 +910,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) char *pstr = NULL; long val = strtol( tstr, &pstr, ('o' == pattern[offset] ? 8 : 16)); - if (pstr != &tstr[2] || val <= 0) + if (pstr != &tstr[2] || val <= 0) { c2_error("Invalid octal/hex escape " "sequence."); + } *(ptptnstr++) = to_char_checked(val); offset += 2; break; @@ -904,8 +924,9 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) *(ptptnstr++) = pattern[offset]; } } - if (!pattern[offset]) + if (!pattern[offset]) { c2_error("Premature end of pattern string."); + } ++offset; *ptptnstr = '\0'; pleaf->ptnstr = strdup(tptnstr); @@ -914,27 +935,32 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) C2H_SKIP_SPACES(); - if (!pleaf->ptntype) + if (!pleaf->ptntype) { c2_error("Invalid pattern type."); + } // Check if the type is correct if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) && C2_L_PTSTRING == pleaf->ptntype) || ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type || C2_L_TDRAWABLE == pleaf->type) && - C2_L_PTINT == pleaf->ptntype))) + C2_L_PTINT == pleaf->ptntype))) { c2_error("Pattern type incompatible with target type."); + } - if (C2_L_PTINT == pleaf->ptntype && pleaf->match) + if (C2_L_PTINT == pleaf->ptntype && pleaf->match) { c2_error("Integer/boolean pattern cannot have operator qualifiers."); + } - if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) + if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) { c2_error("Integer/boolean pattern cannot have flags."); + } if (C2_L_PTSTRING == pleaf->ptntype && (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op || - C2_L_OLTEQ == pleaf->op)) + C2_L_OLTEQ == pleaf->op)) { c2_error("String pattern cannot have an arithmetic operator."); + } return offset; @@ -1173,9 +1199,8 @@ c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) { static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { if (pleaf->predef != C2_L_PUNDEFINED) { return C2_PREDEFS[pleaf->predef].name; - } else { - return pleaf->tgt; } + return pleaf->tgt; } /** @@ -1378,11 +1403,11 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w int word_count = 1; if (pleaf->index < 0) { // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); + auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom); word_count = to_int_checked((prop_info.length + 4 - 1) / 4); } winprop_t prop = x_get_prop_with_offset( - ps->c, wid, pleaf->tgtatom, idx, word_count, + &ps->c, wid, pleaf->tgtatom, idx, word_count, c2_get_atom_type(pleaf), pleaf->format); ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); @@ -1455,11 +1480,11 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w int word_count = 1; if (pleaf->index < 0) { // Get length of property in 32-bit multiples - auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom); + auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom); word_count = to_int_checked((prop_info.length + 4 - 1) / 4); } winprop_t prop = x_get_prop_with_offset( - ps->c, wid, pleaf->tgtatom, idx, word_count, + &ps->c, wid, pleaf->tgtatom, idx, word_count, c2_get_atom_type(pleaf), pleaf->format); ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1)); @@ -1470,7 +1495,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i); if (atom) { xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( - ps->c, xcb_get_atom_name(ps->c, atom), NULL); + ps->c.c, xcb_get_atom_name(ps->c.c, atom), NULL); if (reply) { targets[i] = targets_free_inner[i] = strndup( xcb_get_atom_name_name(reply), @@ -1599,8 +1624,9 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p if (cond.isbranch) { const c2_b_t *pb = cond.b; - if (!pb) + if (!pb) { return false; + } error = false; @@ -1630,8 +1656,9 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p else { const c2_l_t *pleaf = cond.l; - if (!pleaf) + if (!pleaf) { return false; + } c2_match_once_leaf(ps, w, pleaf, &result, &error); @@ -1651,11 +1678,13 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p } // Postprocess the result - if (error) + if (error) { result = false; + } - if (cond.isbranch ? cond.b->neg : cond.l->neg) + if (cond.isbranch ? cond.b->neg : cond.l->neg) { result = !result; + } return result; } @@ -1673,8 +1702,9 @@ bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condl // Then go through the whole linked list for (; condlst; condlst = condlst->next) { if (c2_match_once(ps, w, condlst->ptr)) { - if (pdata) + if (pdata) { *pdata = condlst->data; + } return true; } } diff --git a/src/common.h b/src/common.h index e183a1e6b0..707d00e714 100644 --- a/src/common.h +++ b/src/common.h @@ -84,18 +84,6 @@ struct glx_session; struct atom; struct conv; -enum pending_reply_action { - PENDING_REPLY_ACTION_IGNORE, - PENDING_REPLY_ACTION_ABORT, - PENDING_REPLY_ACTION_DEBUG_ABORT, -}; - -typedef struct pending_reply { - struct pending_reply *next; - unsigned long sequence; - enum pending_reply_action action; -} pending_reply_t; - #ifdef CONFIG_OPENGL #ifdef DEBUG_GLX_DEBUG_CONTEXT typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config, @@ -183,28 +171,14 @@ typedef struct session { struct shader_info *shaders; // === Display related === + /// X connection + struct x_connection c; /// Whether the X server is grabbed by us bool server_grabbed; - /// Display in use. - Display *dpy; - /// Previous handler of X errors - XErrorHandler previous_xerror_handler; - /// Default screen. - int scr; - /// XCB connection. - xcb_connection_t *c; - /// Default visual. - xcb_visualid_t vis; - /// Default depth. - int depth; - /// Root window. - xcb_window_t root; - /// Height of root window. - int root_height; /// Width of root window. int root_width; - // Damage of root window. - // Damage root_damage; + /// Height of root window. + int root_height; /// X Composite overlay window. xcb_window_t overlay; /// The target window for debug mode @@ -289,11 +263,6 @@ typedef struct session { xcb_render_picture_t *alpha_picts; /// Time of last fading. In milliseconds. long long fade_time; - /// Head pointer of the error ignore linked list. - pending_reply_t *pending_reply_head; - /// Pointer to the next member of tail element of the error - /// ignore linked list. - pending_reply_t **pending_reply_tail; // Cached blur convolution kernels. struct x_convolution_kernel **blur_kerns_cache; /// If we should quit @@ -391,10 +360,8 @@ typedef struct session { int glx_event; /// Error base number for X GLX extension. int glx_error; - /// Number of X RandR monitors. - int randr_nmonitors; - /// X RandR monitor regions. - region_t *randr_monitor_regs; + /// Information about monitors. + struct x_monitors monitors; /// Whether X Sync extension exists. bool xsync_exists; /// Event base number for X Sync extension. @@ -492,7 +459,7 @@ static inline struct timespec get_time_timespec(void) { * Return the painting target window. */ static inline xcb_window_t get_tgt_window(session_t *ps) { - return ps->overlay != XCB_NONE ? ps->overlay : ps->root; + return ps->overlay != XCB_NONE ? ps->overlay : ps->c.screen_info->root; } /** @@ -502,35 +469,6 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -static void -set_reply_action(session_t *ps, uint32_t sequence, enum pending_reply_action action) { - auto i = cmalloc(pending_reply_t); - if (!i) { - abort(); - } - - i->sequence = sequence; - i->next = 0; - i->action = action; - *ps->pending_reply_tail = i; - ps->pending_reply_tail = &i->next; -} - -/** - * Ignore X errors caused by given X request. - */ -static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) { - if (ps->o.show_all_xerrors) { - return; - } - - set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_IGNORE); -} - -static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) { - set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_ABORT); -} - /** * Determine if a window has a specific property. * @@ -541,7 +479,8 @@ static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) */ static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t atom) { auto r = xcb_get_property_reply( - ps->c, xcb_get_property(ps->c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL); + ps->c.c, + xcb_get_property(ps->c.c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL); if (!r) { return false; } diff --git a/src/dbus.c b/src/dbus.c index baff2bcc1a..ad98a0f2ae 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1165,7 +1165,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { // display if (!strcmp("display", target)) { - cdbus_reply_string(ps, msg, DisplayString(ps->dpy)); + cdbus_reply_string(ps, msg, DisplayString(ps->c.dpy)); return true; } diff --git a/src/event.c b/src/event.c index b140082874..807cf4ed96 100644 --- a/src/event.c +++ b/src/event.c @@ -57,7 +57,7 @@ static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) { char *name = ""; if (wid) { name = "(Failed to get title)"; - if (ps->root == wid) { + if (ps->c.screen_info->root == wid) { name = "(Root window)"; } else if (ps->overlay == wid) { name = "(Overlay)"; @@ -184,7 +184,7 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { - if (ev->parent == ps->root) { + if (ev->parent == ps->c.screen_info->root) { add_win_top(ps, ev->window); } } @@ -239,7 +239,7 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { } // Recalculate which monitor this window is on - win_update_monitor(ps->randr_nmonitors, ps->randr_monitor_regs, mw); + win_update_monitor(&ps->monitors, mw); } // override_redirect flag cannot be changed after window creation, as far @@ -250,7 +250,7 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) { log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }", ev->event, ev->window, ev->above_sibling, ev->override_redirect); - if (ev->window == ps->root) { + if (ev->window == ps->c.screen_info->root) { set_root_flags(ps, ROOT_FLAGS_CONFIGURED); } else { configure_win(ps, ev); @@ -284,8 +284,8 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { // in redirected state. if (ps->overlay && ev->window == ps->overlay && !ps->redirected) { log_debug("Overlay is mapped while we are not redirected"); - auto e = - xcb_request_check(ps->c, xcb_unmap_window_checked(ps->c, ps->overlay)); + auto e = xcb_request_check( + ps->c.c, xcb_unmap_window_checked(ps->c.c, ps->overlay)); if (e) { log_error("Failed to unmap the overlay window"); free(e); @@ -323,7 +323,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t ps->pending_updates = true; } - if (ev->parent == ps->root) { + if (ev->parent == ps->c.screen_info->root) { // X will generate reparent notifiy even if the parent didn't actually // change (i.e. reparent again to current parent). So we check if that's // the case @@ -351,7 +351,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t // Reset event mask in case something wrong happens xcb_change_window_attributes( - ps->c, ev->window, XCB_CW_EVENT_MASK, + ps->c.c, ev->window, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)}); if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { @@ -360,7 +360,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t "property change in case it gains one.", ev->window); xcb_change_window_attributes( - ps->c, ev->window, XCB_CW_EVENT_MASK, + ps->c.c, ev->window, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) | XCB_EVENT_MASK_PROPERTY_CHANGE}); } else { @@ -373,9 +373,9 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE); ps->pending_updates = true; } else { - if (!w_real_top) + if (!w_real_top) { log_debug("parent %#010x not found", ev->parent); - else { + } else { // Window is not currently mapped, unmark its // client to trigger a client recheck when it is // mapped later. @@ -392,8 +392,9 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { auto w = find_win(ps, ev->window); - if (!w) + if (!w) { return; + } if (ev->place == PlaceOnTop) { restack_top(ps, w); @@ -410,7 +411,8 @@ static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) { } static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) { - if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) { + if (ev->window == ps->c.screen_info->root || + (ps->overlay && ev->window == ps->overlay)) { int more = ev->count + 1; if (ps->n_expose == ps->size_expose) { if (ps->expose_rects) { @@ -439,8 +441,8 @@ static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) { static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) { if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) { // Print out changed atom - xcb_get_atom_name_reply_t *reply = - xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL); + xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( + ps->c.c, xcb_get_atom_name(ps->c.c, ev->atom), NULL); const char *name = "?"; int name_len = 1; if (reply) { @@ -452,7 +454,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t free(reply); } - if (ps->root == ev->window) { + if (ps->c.screen_info->root == ev->window) { if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) { // to update focus ps->pending_updates = true; @@ -473,7 +475,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t // Check whether it could be a client window if (!find_toplevel(ps, ev->window)) { // Reset event mask anyway - xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK, + xcb_change_window_attributes(ps->c.c, ev->window, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask( ps, ev->window, WIN_EVMODE_UNKNOWN)}); @@ -586,12 +588,16 @@ static inline void repair_win(session_t *ps, struct managed_win *w) { if (!w->ever_damaged) { win_extents(w, &parts); - set_ignore_cookie( - ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE)); + if (!ps->o.show_all_xerrors) { + set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage, + XCB_NONE, XCB_NONE)); + } } else { - set_ignore_cookie( - ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, ps->damaged_region)); - x_fetch_region(ps->c, ps->damaged_region, &parts); + if (!ps->o.show_all_xerrors) { + set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, + ps->damaged_region)); + } + x_fetch_region(&ps->c, ps->damaged_region, &parts); pixman_region32_translate(&parts, w->g.x + w->g.border_width, w->g.y + w->g.border_width); } @@ -667,7 +673,7 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { void ev_handle(session_t *ps, xcb_generic_event_t *ev) { if (XCB_EVENT_RESPONSE_TYPE(ev) != KeymapNotify) { - discard_pending(ps, ev->full_sequence); + x_discard_pending(&ps->c, ev->full_sequence); } xcb_window_t wid = ev_window(ps, ev); @@ -689,9 +695,9 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // https://bugs.freedesktop.org/show_bug.cgi?id=35945 // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html auto response_type = XCB_EVENT_RESPONSE_TYPE(ev); - auto proc = XESetWireToEvent(ps->dpy, response_type, 0); + auto proc = XESetWireToEvent(ps->c.dpy, response_type, 0); if (proc) { - XESetWireToEvent(ps->dpy, response_type, proc); + XESetWireToEvent(ps->c.dpy, response_type, proc); XEvent dummy; // Stop Xlib from complaining about lost sequence numbers. @@ -701,8 +707,8 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // // We only need the low 16 bits uint16_t seq = ev->sequence; - ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff); - proc(ps->dpy, &dummy, (xEvent *)ev); + ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->c.dpy) & 0xffff); + proc(ps->c.dpy, &dummy, (xEvent *)ev); // Restore the sequence number ev->sequence = seq; } @@ -736,7 +742,7 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { case SelectionClear: ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev); break; - case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break; + case 0: x_handle_error(&ps->c, (xcb_generic_error_t *)ev); break; default: if (ps->shape_exists && ev->response_type == ps->shape_event) { ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev); diff --git a/src/opengl.c b/src/opengl.c index 5d2d66cb8c..6f4d0471fb 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -39,7 +39,7 @@ static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visuali XVisualInfo vreq = {.visualid = visual}; int nitems = 0; - return XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems); + return XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems); } /** @@ -56,7 +56,7 @@ bool glx_init(session_t *ps, bool need_render) { } // Get XVisualInfo - pvis = get_visualinfo_from_visual(ps, ps->vis); + pvis = get_visualinfo_from_visual(ps, ps->c.screen_info->root_visual); if (!pvis) { log_error("Failed to acquire XVisualInfo for current visual."); goto glx_init_end; @@ -65,12 +65,13 @@ bool glx_init(session_t *ps, bool need_render) { // Ensure the visual is double-buffered if (need_render) { int value = 0; - if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) { + if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) { log_error("Root visual is not a GL visual."); goto glx_init_end; } - if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { + if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) || + !value) { log_error("Root visual is not a double buffered GL visual."); goto glx_init_end; } @@ -112,7 +113,7 @@ bool glx_init(session_t *ps, bool need_render) { if (!psglx->context) { // Get GLX context #ifndef DEBUG_GLX_DEBUG_CONTEXT - psglx->context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE); + psglx->context = glXCreateContext(ps->c.dpy, pvis, None, GL_TRUE); #else { GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis); @@ -134,7 +135,7 @@ bool glx_init(session_t *ps, bool need_render) { static const int attrib_list[] = { GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None}; psglx->context = p_glXCreateContextAttribsARB( - ps->dpy, fbconfig, NULL, GL_TRUE, attrib_list); + ps->c.dpy, fbconfig, NULL, GL_TRUE, attrib_list); } #endif @@ -144,7 +145,7 @@ bool glx_init(session_t *ps, bool need_render) { } // Attach GLX context - if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), psglx->context)) { + if (!glXMakeCurrent(ps->c.dpy, get_tgt_window(ps), psglx->context)) { log_error("Failed to attach GLX context."); goto glx_init_end; } @@ -201,7 +202,7 @@ bool glx_init(session_t *ps, bool need_render) { // Clear screen glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // glXSwapBuffers(ps->dpy, get_tgt_window(ps)); + // glXSwapBuffers(ps->c.dpy, get_tgt_window(ps)); } success = true; @@ -266,8 +267,8 @@ void glx_destroy(session_t *ps) { // Destroy GLX context if (ps->psglx->context) { - glXMakeCurrent(ps->dpy, None, NULL); - glXDestroyContext(ps->dpy, ps->psglx->context); + glXMakeCurrent(ps->c.dpy, None, NULL); + glXDestroyContext(ps->c.dpy, ps->psglx->context); ps->psglx->context = NULL; } @@ -732,7 +733,7 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, // Retrieve pixmap parameters, if they aren't provided if (!width || !height) { auto r = xcb_get_geometry_reply( - ps->c, xcb_get_geometry(ps->c, pixmap), NULL); + ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL); if (!r) { log_error("Failed to query info of pixmap %#010x.", pixmap); return false; @@ -773,7 +774,7 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, 0, }; - ptex->glpixmap = glXCreatePixmap(ps->dpy, fbcfg->cfg, pixmap, attrs); + ptex->glpixmap = glXCreatePixmap(ps->c.dpy, fbcfg->cfg, pixmap, attrs); ptex->pixmap = pixmap; ptex->target = (GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE); @@ -820,9 +821,9 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, // The specification requires rebinding whenever the content changes... // We can't follow this, too slow. if (need_release) - glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); + glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); - glXBindTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); + glXBindTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); // Cleanup glBindTexture(ptex->target, 0); @@ -840,13 +841,13 @@ void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { // Release binding if (ptex->glpixmap && ptex->texture) { glBindTexture(ptex->target, ptex->texture); - glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); + glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(ptex->target, 0); } // Free GLX Pixmap if (ptex->glpixmap) { - glXDestroyPixmap(ps->dpy, ptex->glpixmap); + glXDestroyPixmap(ps->c.dpy, ptex->glpixmap); ptex->glpixmap = 0; } diff --git a/src/picom.c b/src/picom.c index 126922e732..1567ab8962 100644 --- a/src/picom.c +++ b/src/picom.c @@ -129,7 +129,7 @@ static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { auto ps = session_ptr(w, dpms_check_timer); - auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); if (!r) { log_fatal("Failed to query DPMS status."); abort(); @@ -149,7 +149,7 @@ void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { * XXX move to win.c */ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) { - if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) { + if (!wid || PointerRoot == wid || wid == ps->c.screen_info->root || wid == ps->overlay) { return NULL; } @@ -352,8 +352,9 @@ void add_damage(session_t *ps, const region_t *damage) { */ static double fade_timeout(session_t *ps) { auto now = get_time_ms(); - if (ps->o.fade_delta + ps->fade_time < now) + if (ps->o.fade_delta + ps->fade_time < now) { return 0; + } auto diff = ps->o.fade_delta + ps->fade_time - now; @@ -406,47 +407,6 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) { return true; } -// === Error handling === - -void discard_pending(session_t *ps, uint32_t sequence) { - while (ps->pending_reply_head) { - if (sequence > ps->pending_reply_head->sequence) { - auto next = ps->pending_reply_head->next; - free(ps->pending_reply_head); - ps->pending_reply_head = next; - if (!ps->pending_reply_head) { - ps->pending_reply_tail = &ps->pending_reply_head; - } - } else { - break; - } - } -} - -static void handle_error(session_t *ps, xcb_generic_error_t *ev) { - if (ps == NULL) { - // Do not ignore errors until the session has been initialized - return; - } - discard_pending(ps, ev->full_sequence); - if (ps->pending_reply_head && ps->pending_reply_head->sequence == ev->full_sequence) { - if (ps->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) { - x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, - ev->minor_code, ev->error_code); - } - switch (ps->pending_reply_head->action) { - case PENDING_REPLY_ACTION_ABORT: - log_fatal("An unrecoverable X error occurred, aborting..."); - abort(); - case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break; - case PENDING_REPLY_ACTION_IGNORE: break; - } - return; - } - x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code, - ev->error_code); -} - // === Windows === /** @@ -482,8 +442,8 @@ uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { */ void update_ewmh_active_win(session_t *ps) { // Search for the window - xcb_window_t wid = - wid_get_prop_window(ps->c, ps->root, ps->atoms->a_NET_ACTIVE_WINDOW); + xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root, + ps->atoms->a_NET_ACTIVE_WINDOW); auto w = find_win_all(ps, wid); // Mark the window focused. No need to unfocus the previous one. @@ -510,7 +470,7 @@ static void recheck_focus(session_t *ps) { // opacity on it xcb_window_t wid = XCB_NONE; xcb_get_input_focus_reply_t *reply = - xcb_get_input_focus_reply(ps->c, xcb_get_input_focus(ps->c), NULL); + xcb_get_input_focus_reply(ps->c.c, xcb_get_input_focus(ps->c.c), NULL); if (reply) { wid = reply->focus; @@ -719,7 +679,7 @@ static bool initialize_backend(session_t *ps) { /// Handle configure event of the root window static void configure_root(session_t *ps) { - auto r = XCB_AWAIT(xcb_get_geometry, ps->c, ps->root); + 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(); @@ -795,8 +755,8 @@ static void configure_root(session_t *ps) { static void handle_root_flags(session_t *ps) { if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) { - if (ps->o.crop_shadow_to_monitor) { - x_update_randr_monitors(ps); + if (ps->o.crop_shadow_to_monitor && ps->randr_exists) { + x_update_monitors(&ps->c, &ps->monitors); } ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; } @@ -1096,10 +1056,11 @@ void root_damaged(session_t *ps) { 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->root, ps->atoms); + auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms); if (pixmap != XCB_NONE) { ps->root_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false); + ps->backend_data, pixmap, + x_get_visual_info(&ps->c, ps->c.screen_info->root_visual), false); if (ps->root_image) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE, @@ -1114,27 +1075,6 @@ void root_damaged(session_t *ps) { force_repaint(ps); } -/** - * Xlib error handler function. - */ -static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - // Fake a xcb error, fill in just enough information - xcb_generic_error_t xcb_err; - xcb_err.full_sequence = (uint32_t)ev->serial; - xcb_err.major_code = ev->request_code; - xcb_err.minor_code = ev->minor_code; - xcb_err.error_code = ev->error_code; - handle_error(ps_g, &xcb_err); - return 0; -} - -/** - * XCB error handler function. - */ -void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - handle_error(ps, err); -} - /** * Force a full-screen repaint. */ @@ -1169,10 +1109,11 @@ void opts_set_no_fading_openclose(session_t *ps, bool newval) { static int register_cm(session_t *ps) { assert(!ps->reg_win); - ps->reg_win = x_new_id(ps->c); + ps->reg_win = x_new_id(&ps->c); auto e = xcb_request_check( - ps->c, xcb_create_window_checked(ps->c, XCB_COPY_FROM_PARENT, ps->reg_win, ps->root, - 0, 0, 1, 1, 0, XCB_NONE, ps->vis, 0, NULL)); + ps->c.c, xcb_create_window_checked(ps->c.c, XCB_COPY_FROM_PARENT, ps->reg_win, + ps->c.screen_info->root, 0, 0, 1, 1, 0, XCB_NONE, + ps->c.screen_info->root_visual, 0, NULL)); if (e) { log_fatal("Failed to create window."); @@ -1191,10 +1132,10 @@ static int register_cm(session_t *ps) { // Set names and classes for (size_t i = 0; i < ARR_SIZE(prop_atoms); i++) { e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, prop_atoms[i], - prop_is_utf8[i] ? ps->atoms->aUTF8_STRING : XCB_ATOM_STRING, - 8, strlen("picom"), "picom")); + ps->c.c, xcb_change_property_checked( + ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win, prop_atoms[i], + prop_is_utf8[i] ? ps->atoms->aUTF8_STRING : XCB_ATOM_STRING, + 8, strlen("picom"), "picom")); if (e) { log_error_x_error(e, "Failed to set window property %d", prop_atoms[i]); @@ -1204,9 +1145,9 @@ static int register_cm(session_t *ps) { const char picom_class[] = "picom\0picom"; e = xcb_request_check( - ps->c, xcb_change_property_checked(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - ps->atoms->aWM_CLASS, XCB_ATOM_STRING, 8, - ARR_SIZE(picom_class), picom_class)); + ps->c.c, xcb_change_property_checked(ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win, + ps->atoms->aWM_CLASS, XCB_ATOM_STRING, 8, + ARR_SIZE(picom_class), picom_class)); if (e) { log_error_x_error(e, "Failed to set the WM_CLASS property"); free(e); @@ -1220,10 +1161,10 @@ static int register_cm(session_t *ps) { if (gethostname(hostname, hostname_max) == 0) { e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - ps->atoms->aWM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, - (uint32_t)strlen(hostname), hostname)); + ps->c.c, xcb_change_property_checked( + ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win, + ps->atoms->aWM_CLIENT_MACHINE, XCB_ATOM_STRING, + 8, (uint32_t)strlen(hostname), hostname)); if (e) { log_error_x_error(e, "Failed to set the WM_CLIENT_MACHINE" " property"); @@ -1239,16 +1180,16 @@ static int register_cm(session_t *ps) { // Set _NET_WM_PID { auto pid = getpid(); - xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, + xcb_change_property(ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win, ps->atoms->a_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid); } // Set COMPTON_VERSION e = xcb_request_check( - ps->c, xcb_change_property_checked( - ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8, - (uint32_t)strlen(PICOM_VERSION), PICOM_VERSION)); + ps->c.c, xcb_change_property_checked( + ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win, + get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8, + (uint32_t)strlen(PICOM_VERSION), PICOM_VERSION)); if (e) { log_error_x_error(e, "Failed to set COMPTON_VERSION."); free(e); @@ -1260,7 +1201,7 @@ static int register_cm(session_t *ps) { xcb_atom_t atom; char *buf = NULL; - if (asprintf(&buf, "%s%d", register_prop, ps->scr) < 0) { + if (asprintf(&buf, "%s%d", register_prop, ps->c.screen) < 0) { log_fatal("Failed to allocate memory"); return -1; } @@ -1268,7 +1209,7 @@ static int register_cm(session_t *ps) { free(buf); xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply( - ps->c, xcb_get_selection_owner(ps->c, atom), NULL); + ps->c.c, xcb_get_selection_owner(ps->c.c, atom), NULL); if (reply && reply->owner != XCB_NONE) { // Another compositor already running @@ -1276,7 +1217,7 @@ static int register_cm(session_t *ps) { return 1; } free(reply); - xcb_set_selection_owner(ps->c, ps->reg_win, atom, 0); + xcb_set_selection_owner(ps->c.c, ps->reg_win, atom, 0); } return 0; @@ -1306,9 +1247,8 @@ static inline bool write_pid(session_t *ps) { * Initialize X composite overlay window. */ static bool init_overlay(session_t *ps) { - xcb_composite_get_overlay_window_reply_t *reply = - xcb_composite_get_overlay_window_reply( - ps->c, xcb_composite_get_overlay_window(ps->c, ps->root), NULL); + xcb_composite_get_overlay_window_reply_t *reply = xcb_composite_get_overlay_window_reply( + ps->c.c, xcb_composite_get_overlay_window(ps->c.c, ps->c.screen_info->root), NULL); if (reply) { ps->overlay = reply->overlay_win; free(reply); @@ -1318,13 +1258,13 @@ static bool init_overlay(session_t *ps) { if (ps->overlay != XCB_NONE) { // Set window region of the overlay window, code stolen from // compiz-0.8.8 - if (!XCB_AWAIT_VOID(xcb_shape_mask, ps->c, XCB_SHAPE_SO_SET, + if (!XCB_AWAIT_VOID(xcb_shape_mask, ps->c.c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, ps->overlay, 0, 0, 0)) { log_fatal("Failed to set the bounding shape of overlay, giving " "up."); return false; } - if (!XCB_AWAIT_VOID(xcb_shape_rectangles, ps->c, XCB_SHAPE_SO_SET, + if (!XCB_AWAIT_VOID(xcb_shape_rectangles, ps->c.c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, ps->overlay, 0, 0, 0, NULL)) { log_fatal("Failed to set the input shape of overlay, giving up."); @@ -1332,7 +1272,7 @@ static bool init_overlay(session_t *ps) { } // Listen to Expose events on the overlay - xcb_change_window_attributes(ps->c, ps->overlay, XCB_CW_EVENT_MASK, + xcb_change_window_attributes(ps->c.c, ps->overlay, XCB_CW_EVENT_MASK, (const uint32_t[]){XCB_EVENT_MASK_EXPOSURE}); // Retrieve DamageNotify on root window if we are painting on an @@ -1340,7 +1280,7 @@ static bool init_overlay(session_t *ps) { // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty); // Unmap the overlay, we will map it when needed in redirect_start - XCB_AWAIT_VOID(xcb_unmap_window, ps->c, ps->overlay); + XCB_AWAIT_VOID(xcb_unmap_window, ps->c.c, ps->overlay); } else { log_error("Cannot get X Composite overlay window. Falling " "back to painting on root window."); @@ -1351,27 +1291,29 @@ static bool init_overlay(session_t *ps) { } static bool init_debug_window(session_t *ps) { - xcb_colormap_t colormap = x_new_id(ps->c); - ps->debug_window = x_new_id(ps->c); + xcb_colormap_t colormap = x_new_id(&ps->c); + ps->debug_window = x_new_id(&ps->c); auto err = xcb_request_check( - ps->c, xcb_create_colormap_checked(ps->c, XCB_COLORMAP_ALLOC_NONE, colormap, - ps->root, ps->vis)); + ps->c.c, xcb_create_colormap_checked(ps->c.c, XCB_COLORMAP_ALLOC_NONE, + colormap, ps->c.screen_info->root, + ps->c.screen_info->root_visual)); if (err) { goto err_out; } err = xcb_request_check( - ps->c, xcb_create_window_checked(ps->c, (uint8_t)ps->depth, ps->debug_window, - ps->root, 0, 0, to_u16_checked(ps->root_width), - to_u16_checked(ps->root_height), 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, ps->vis, - XCB_CW_COLORMAP, (uint32_t[]){colormap, 0})); + ps->c.c, xcb_create_window_checked( + ps->c.c, (uint8_t)ps->c.screen_info->root_depth, + ps->debug_window, ps->c.screen_info->root, 0, 0, + to_u16_checked(ps->root_width), to_u16_checked(ps->root_height), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, ps->c.screen_info->root_visual, + XCB_CW_COLORMAP, (uint32_t[]){colormap, 0})); if (err) { goto err_out; } - err = xcb_request_check(ps->c, xcb_map_window_checked(ps->c, ps->debug_window)); + err = xcb_request_check(ps->c.c, xcb_map_window_checked(ps->c.c, ps->debug_window)); if (err) { goto err_out; } @@ -1386,7 +1328,7 @@ xcb_window_t session_get_target_window(session_t *ps) { if (ps->o.debug_mode) { return ps->debug_window; } - return ps->overlay != XCB_NONE ? ps->overlay : ps->root; + return ps->overlay != XCB_NONE ? ps->overlay : ps->c.screen_info->root; } uint8_t session_redirection_mode(session_t *ps) { @@ -1416,18 +1358,18 @@ static bool redirect_start(session_t *ps) { // Map overlay window. Done firstly according to this: // https://bugzilla.gnome.org/show_bug.cgi?id=597014 if (ps->overlay != XCB_NONE) { - xcb_map_window(ps->c, ps->overlay); + xcb_map_window(ps->c.c, ps->overlay); } - bool success = XCB_AWAIT_VOID(xcb_composite_redirect_subwindows, ps->c, ps->root, - session_redirection_mode(ps)); + bool success = XCB_AWAIT_VOID(xcb_composite_redirect_subwindows, ps->c.c, + ps->c.screen_info->root, session_redirection_mode(ps)); if (!success) { log_fatal("Another composite manager is already running " "(and does not handle _NET_WM_CM_Sn correctly)"); return false; } - x_sync(ps->c); + x_sync(&ps->c); if (!initialize_backend(ps)) { return false; @@ -1456,16 +1398,16 @@ static bool redirect_start(session_t *ps) { } if (ps->present_exists && ps->frame_pacing) { - ps->present_event_id = x_new_id(ps->c); + ps->present_event_id = x_new_id(&ps->c); auto select_input = xcb_present_select_input( - ps->c, ps->present_event_id, session_get_target_window(ps), + ps->c.c, ps->present_event_id, session_get_target_window(ps), XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); - auto notify_msc = - xcb_present_notify_msc(ps->c, session_get_target_window(ps), 0, 0, 1, 0); - set_cant_fail_cookie(ps, select_input); - set_cant_fail_cookie(ps, notify_msc); + auto notify_msc = xcb_present_notify_msc( + ps->c.c, session_get_target_window(ps), 0, 0, 1, 0); + set_cant_fail_cookie(&ps->c, select_input); + set_cant_fail_cookie(&ps->c, notify_msc); ps->present_event = xcb_register_for_special_xge( - ps->c, &xcb_present_id, ps->present_event_id, NULL); + ps->c.c, &xcb_present_id, ps->present_event_id, NULL); // Initialize rendering and frame timing statistics, and frame pacing // states. @@ -1480,13 +1422,13 @@ static bool redirect_start(session_t *ps) { } // Must call XSync() here - x_sync(ps->c); + x_sync(&ps->c); ps->redirected = true; ps->first_frame = true; // Re-detect driver since we now have a backend - ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); + ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root); apply_driver_workarounds(ps, ps->drivers); root_damaged(ps); @@ -1506,10 +1448,11 @@ static void unredirect(session_t *ps) { destroy_backend(ps); - xcb_composite_unredirect_subwindows(ps->c, ps->root, session_redirection_mode(ps)); + xcb_composite_unredirect_subwindows(ps->c.c, ps->c.screen_info->root, + session_redirection_mode(ps)); // Unmap overlay window if (ps->overlay != XCB_NONE) { - xcb_unmap_window(ps->c, ps->overlay); + xcb_unmap_window(ps->c.c, ps->overlay); } // Free the damage ring @@ -1521,15 +1464,15 @@ static void unredirect(session_t *ps) { ps->damage_ring = ps->damage = NULL; if (ps->present_event_id) { - xcb_present_select_input(ps->c, ps->present_event_id, + xcb_present_select_input(ps->c.c, ps->present_event_id, session_get_target_window(ps), 0); ps->present_event_id = XCB_NONE; - xcb_unregister_for_special_event(ps->c, ps->present_event); + xcb_unregister_for_special_event(ps->c.c, ps->present_event); ps->present_event = NULL; } // Must call XSync() here - x_sync(ps->c); + x_sync(&ps->c); ps->redirected = false; log_debug("Screen unredirected."); @@ -1553,9 +1496,9 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ next_msc = ps->last_msc + 1; event_is_invalid = true; } - auto cookie = xcb_present_notify_msc(ps->c, session_get_target_window(ps), - 0, next_msc, 0, 0); - set_cant_fail_cookie(ps, cookie); + auto cookie = xcb_present_notify_msc( + ps->c.c, session_get_target_window(ps), 0, next_msc, 0, 0); + set_cant_fail_cookie(&ps->c, cookie); } if (event_is_invalid) { return; @@ -1602,7 +1545,7 @@ static void handle_present_events(session_t *ps) { return; } xcb_present_generic_event_t *ev; - while ((ev = (void *)xcb_poll_for_special_event(ps->c, ps->present_event))) { + while ((ev = (void *)xcb_poll_for_special_event(ps->c.c, ps->present_event))) { if (ev->event != ps->present_event_id) { // This event doesn't have the right event context, it's not meant // for us. @@ -1623,7 +1566,7 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents handle_present_events(ps); xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_queued_event(ps->c))) { + while ((ev = xcb_poll_for_queued_event(ps->c.c))) { ev_handle(ps, ev); free(ev); }; @@ -1632,9 +1575,9 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents // for an indefinite amount of time. // Use XFlush here too, we might still use some Xlib functions // because OpenGL. - XFlush(ps->dpy); - xcb_flush(ps->c); - int err = xcb_connection_has_error(ps->c); + XFlush(ps->c.dpy); + xcb_flush(ps->c.c); + int err = xcb_connection_has_error(ps->c.c); if (err) { log_fatal("X11 server connection broke (error %d)", err); exit(1); @@ -1692,7 +1635,7 @@ static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_ static void handle_pending_updates(EV_P_ struct session *ps) { if (ps->pending_updates) { log_debug("Delayed handling of events, entering critical section"); - auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); + auto e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c)); if (e) { log_fatal_x_error(e, "failed to grab x server"); free(e); @@ -1718,7 +1661,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { { auto r = xcb_get_input_focus_reply( - ps->c, xcb_get_input_focus(ps->c), NULL); + ps->c.c, xcb_get_input_focus(ps->c.c), NULL); if (!ps->active_win || (r && r->focus != ps->active_win->base.id)) { recheck_focus(ps); } @@ -1728,7 +1671,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { // Process window flags (stale images) refresh_images(ps); - e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); + e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c)); if (e) { log_fatal_x_error(e, "failed to ungrab x server"); free(e); @@ -1868,7 +1811,7 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) { static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { session_t *ps = (session_t *)w; - xcb_generic_event_t *ev = xcb_poll_for_event(ps->c); + xcb_generic_event_t *ev = xcb_poll_for_event(ps->c.c); if (ev) { ev_handle(ps, ev); free(ev); @@ -1966,12 +1909,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, const char *config_file, bool all_xerrors, bool fork) { static const session_t s_def = { .backend_data = NULL, - .dpy = NULL, - .scr = 0, - .c = NULL, - .vis = 0, - .depth = 0, - .root = XCB_NONE, .root_height = 0, .root_width = 0, // .root_damage = XCB_NONE, @@ -1987,8 +1924,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .redirected = false, .alpha_picts = NULL, .fade_time = 0L, - .pending_reply_head = NULL, - .pending_reply_tail = NULL, .quit = false, .expose_rects = NULL, @@ -2056,52 +1991,42 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // TODO(yshui) investigate what's the best window size render_statistics_init(&ps->render_stats, 128); - ps->pending_reply_tail = &ps->pending_reply_head; - ps->o.show_all_xerrors = all_xerrors; // Use the same Display across reset, primarily for resource leak checking - ps->dpy = dpy; - ps->c = XGetXCBConnection(ps->dpy); + x_connection_init(&ps->c, dpy); + // We store width/height from screen_info instead using them directly because they + // can change, see configure_root(). + ps->root_width = ps->c.screen_info->width_in_pixels; + ps->root_height = ps->c.screen_info->height_in_pixels; const xcb_query_extension_reply_t *ext_info; - ps->previous_xerror_handler = XSetErrorHandler(xerror); - - ps->scr = DefaultScreen(ps->dpy); - - auto screen = x_screen_of_display(ps->c, ps->scr); - ps->vis = screen->root_visual; - ps->depth = screen->root_depth; - ps->root = screen->root; - ps->root_width = screen->width_in_pixels; - ps->root_height = screen->height_in_pixels; - // Start listening to events on root earlier to catch all possible // root geometry changes auto e = xcb_request_check( - ps->c, xcb_change_window_attributes_checked( - ps->c, ps->root, XCB_CW_EVENT_MASK, - (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | - XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | - XCB_EVENT_MASK_PROPERTY_CHANGE})); + ps->c.c, xcb_change_window_attributes_checked( + ps->c.c, ps->c.screen_info->root, XCB_CW_EVENT_MASK, + (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE})); if (e) { log_error_x_error(e, "Failed to setup root window event mask"); free(e); } - xcb_prefetch_extension_data(ps->c, &xcb_render_id); - xcb_prefetch_extension_data(ps->c, &xcb_composite_id); - xcb_prefetch_extension_data(ps->c, &xcb_damage_id); - xcb_prefetch_extension_data(ps->c, &xcb_shape_id); - xcb_prefetch_extension_data(ps->c, &xcb_xfixes_id); - xcb_prefetch_extension_data(ps->c, &xcb_randr_id); - xcb_prefetch_extension_data(ps->c, &xcb_present_id); - xcb_prefetch_extension_data(ps->c, &xcb_sync_id); - xcb_prefetch_extension_data(ps->c, &xcb_glx_id); - xcb_prefetch_extension_data(ps->c, &xcb_dpms_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_render_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_composite_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_damage_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_shape_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_xfixes_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_randr_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_present_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_sync_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_glx_id); + xcb_prefetch_extension_data(ps->c.c, &xcb_dpms_id); - ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_render_id); if (!ext_info || !ext_info->present) { log_fatal("No render extension"); exit(1); @@ -2109,7 +2034,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->render_event = ext_info->first_event; ps->render_error = ext_info->first_error; - ext_info = xcb_get_extension_data(ps->c, &xcb_composite_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_composite_id); if (!ext_info || !ext_info->present) { log_fatal("No composite extension"); exit(1); @@ -2120,8 +2045,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, { xcb_composite_query_version_reply_t *reply = xcb_composite_query_version_reply( - ps->c, - xcb_composite_query_version(ps->c, XCB_COMPOSITE_MAJOR_VERSION, + ps->c.c, + xcb_composite_query_version(ps->c.c, XCB_COMPOSITE_MAJOR_VERSION, XCB_COMPOSITE_MINOR_VERSION), NULL); @@ -2133,45 +2058,45 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(reply); } - ext_info = xcb_get_extension_data(ps->c, &xcb_damage_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_damage_id); if (!ext_info || !ext_info->present) { log_fatal("No damage extension"); exit(1); } ps->damage_event = ext_info->first_event; ps->damage_error = ext_info->first_error; - xcb_discard_reply(ps->c, xcb_damage_query_version(ps->c, XCB_DAMAGE_MAJOR_VERSION, - XCB_DAMAGE_MINOR_VERSION) - .sequence); + xcb_discard_reply(ps->c.c, xcb_damage_query_version(ps->c.c, XCB_DAMAGE_MAJOR_VERSION, + XCB_DAMAGE_MINOR_VERSION) + .sequence); - ext_info = xcb_get_extension_data(ps->c, &xcb_xfixes_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_xfixes_id); if (!ext_info || !ext_info->present) { log_fatal("No XFixes extension"); exit(1); } ps->xfixes_event = ext_info->first_event; ps->xfixes_error = ext_info->first_error; - xcb_discard_reply(ps->c, xcb_xfixes_query_version(ps->c, XCB_XFIXES_MAJOR_VERSION, - XCB_XFIXES_MINOR_VERSION) - .sequence); + xcb_discard_reply(ps->c.c, xcb_xfixes_query_version(ps->c.c, XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION) + .sequence); - ps->damaged_region = x_new_id(ps->c); - if (!XCB_AWAIT_VOID(xcb_xfixes_create_region, ps->c, ps->damaged_region, 0, NULL)) { + ps->damaged_region = x_new_id(&ps->c); + if (!XCB_AWAIT_VOID(xcb_xfixes_create_region, ps->c.c, ps->damaged_region, 0, NULL)) { log_fatal("Failed to create a XFixes region"); goto err; } - ext_info = xcb_get_extension_data(ps->c, &xcb_glx_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_glx_id); if (ext_info && ext_info->present) { ps->glx_exists = true; ps->glx_error = ext_info->first_error; ps->glx_event = ext_info->first_event; } - ext_info = xcb_get_extension_data(ps->c, &xcb_dpms_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_dpms_id); ps->dpms_exists = ext_info && ext_info->present; if (ps->dpms_exists) { - auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); if (!r) { log_fatal("Failed to query DPMS info"); goto err; @@ -2226,7 +2151,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, "binary will not be installed in the future."); } - ps->atoms = init_atoms(ps->c); + ps->atoms = init_atoms(ps->c.c); ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0; #define SET_WM_TYPE_ATOM(x) \ ps->atoms_wintypes[WINTYPE_##x] = ps->atoms->a_NET_WM_WINDOW_TYPE_##x @@ -2287,25 +2212,25 @@ static session_t *session_init(int argc, char **argv, Display *dpy, rebuild_shadow_exclude_reg(ps); // Query X Shape - ext_info = xcb_get_extension_data(ps->c, &xcb_shape_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_shape_id); if (ext_info && ext_info->present) { ps->shape_event = ext_info->first_event; ps->shape_error = ext_info->first_error; ps->shape_exists = true; } - ext_info = xcb_get_extension_data(ps->c, &xcb_randr_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_randr_id); if (ext_info && ext_info->present) { ps->randr_exists = true; ps->randr_event = ext_info->first_event; ps->randr_error = ext_info->first_error; } - ext_info = xcb_get_extension_data(ps->c, &xcb_present_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_present_id); if (ext_info && ext_info->present) { auto r = xcb_present_query_version_reply( - ps->c, - xcb_present_query_version(ps->c, XCB_PRESENT_MAJOR_VERSION, + ps->c.c, + xcb_present_query_version(ps->c.c, XCB_PRESENT_MAJOR_VERSION, XCB_PRESENT_MINOR_VERSION), NULL); if (r) { @@ -2315,14 +2240,14 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } // Query X Sync - ext_info = xcb_get_extension_data(ps->c, &xcb_sync_id); + ext_info = xcb_get_extension_data(ps->c.c, &xcb_sync_id); if (ext_info && ext_info->present) { ps->xsync_error = ext_info->first_error; ps->xsync_event = ext_info->first_event; // Need X Sync 3.1 for fences auto r = xcb_sync_initialize_reply( - ps->c, - xcb_sync_initialize(ps->c, XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION), + ps->c.c, + xcb_sync_initialize(ps->c.c, XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION), NULL); if (r && (r->major_version > 3 || (r->major_version == 3 && r->minor_version >= 1))) { @@ -2333,9 +2258,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->sync_fence = XCB_NONE; if (ps->xsync_exists) { - ps->sync_fence = x_new_id(ps->c); - e = xcb_request_check(ps->c, xcb_sync_create_fence_checked( - ps->c, ps->root, ps->sync_fence, 0)); + ps->sync_fence = x_new_id(&ps->c); + e = xcb_request_check( + ps->c.c, xcb_sync_create_fence_checked( + ps->c.c, ps->c.screen_info->root, ps->sync_fence, 0)); if (e) { if (ps->o.xrender_sync_fence) { log_error_x_error(e, "Failed to create a XSync fence. " @@ -2412,7 +2338,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } - ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); + ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root); apply_driver_workarounds(ps, ps->drivers); // Initialize filters, must be preceded by OpenGL context creation @@ -2453,26 +2379,29 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // Monitor screen changes if vsync_sw is enabled and we are using // an auto-detected refresh rate, or when X RandR features are enabled if (ps->randr_exists && ps->o.crop_shadow_to_monitor) { - xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); + xcb_randr_select_input(ps->c.c, ps->c.screen_info->root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); + x_update_monitors(&ps->c, &ps->monitors); } - x_update_randr_monitors(ps); - { xcb_render_create_picture_value_list_t pa = { .subwindowmode = IncludeInferiors, }; ps->root_picture = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + &ps->c, ps->c.screen_info->root_visual, ps->c.screen_info->root, + XCB_RENDER_CP_SUBWINDOW_MODE, &pa); if (ps->overlay != XCB_NONE) { ps->tgt_picture = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->overlay, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - } else + &ps->c, ps->c.screen_info->root_visual, ps->overlay, + XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + } else { ps->tgt_picture = ps->root_picture; + } } - ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ); + ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->c.dpy), EV_READ); ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); ev_init(&ps->draw_timer, draw_callback); @@ -2509,7 +2438,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // functions if (ps->o.dbus) { #ifdef CONFIG_DBUS - cdbus_init(ps, DisplayString(ps->dpy)); + cdbus_init(ps, DisplayString(ps->c.dpy)); if (!ps->dbus_data) { ps->o.dbus = false; } @@ -2519,7 +2448,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, #endif } - e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); + e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c)); if (e) { log_fatal_x_error(e, "Failed to grab X server"); free(e); @@ -2532,12 +2461,12 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // earlier is irrelavant at this point. // A better solution is probably grabbing the server from the very start. But I // think there still could be race condition that mandates discarding the events. - x_discard_events(ps->c); + x_discard_events(&ps->c); - xcb_query_tree_reply_t *query_tree_reply = - xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, ps->root), NULL); + xcb_query_tree_reply_t *query_tree_reply = xcb_query_tree_reply( + ps->c.c, xcb_query_tree(ps->c.c, ps->c.screen_info->root), NULL); - e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); + e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c)); if (e) { log_fatal_x_error(e, "Failed to ungrab server"); free(e); @@ -2630,7 +2559,7 @@ static void session_destroy(session_t *ps) { ps->file_watch_handle = NULL; // Stop listening to events on root window - xcb_change_window_attributes(ps->c, ps->root, XCB_CW_EVENT_MASK, + xcb_change_window_attributes(ps->c.c, ps->c.screen_info->root, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); #ifdef CONFIG_DBUS @@ -2681,32 +2610,17 @@ static void session_destroy(session_t *ps) { ps->track_atom_lst = NULL; } - // Free ignore linked list - { - pending_reply_t *next = NULL; - for (auto ign = ps->pending_reply_head; ign; ign = next) { - next = ign->next; - - free(ign); - } - - // Reset head and tail - ps->pending_reply_head = NULL; - ps->pending_reply_tail = &ps->pending_reply_head; - } - // Free tgt_{buffer,picture} and root_picture if (ps->tgt_buffer.pict == ps->tgt_picture) { ps->tgt_buffer.pict = XCB_NONE; } - if (ps->tgt_picture == ps->root_picture) { - ps->tgt_picture = XCB_NONE; - } else { - free_picture(ps->c, &ps->tgt_picture); + if (ps->tgt_picture != ps->root_picture) { + x_free_picture(&ps->c, ps->tgt_picture); } + x_free_picture(&ps->c, ps->root_picture); + ps->tgt_picture = ps->root_picture = XCB_NONE; - free_picture(ps->c, &ps->root_picture); free_paint(ps, &ps->tgt_buffer); pixman_region32_fini(&ps->screen_reg); @@ -2719,7 +2633,7 @@ static void session_destroy(session_t *ps) { } free(ps->o.blur_kerns); free(ps->o.glx_fshader_win_str); - x_free_randr_info(ps); + x_free_monitor_info(&ps->monitors); render_statistics_destroy(&ps->render_stats); @@ -2744,28 +2658,28 @@ static void session_destroy(session_t *ps) { // Release overlay window if (ps->overlay) { - xcb_composite_release_overlay_window(ps->c, ps->overlay); + xcb_composite_release_overlay_window(ps->c.c, ps->overlay); ps->overlay = XCB_NONE; } if (ps->sync_fence != XCB_NONE) { - xcb_sync_destroy_fence(ps->c, ps->sync_fence); + xcb_sync_destroy_fence(ps->c.c, ps->sync_fence); ps->sync_fence = XCB_NONE; } // Free reg_win if (ps->reg_win != XCB_NONE) { - xcb_destroy_window(ps->c, ps->reg_win); + xcb_destroy_window(ps->c.c, ps->reg_win); ps->reg_win = XCB_NONE; } if (ps->debug_window != XCB_NONE) { - xcb_destroy_window(ps->c, ps->debug_window); + xcb_destroy_window(ps->c.c, ps->debug_window); ps->debug_window = XCB_NONE; } if (ps->damaged_region != XCB_NONE) { - xcb_xfixes_destroy_region(ps->c, ps->damaged_region); + xcb_xfixes_destroy_region(ps->c.c, ps->damaged_region); ps->damaged_region = XCB_NONE; } @@ -2784,7 +2698,7 @@ static void session_destroy(session_t *ps) { #endif // Flush all events - x_sync(ps->c); + x_sync(&ps->c); ev_io_stop(ps->loop, &ps->xiow); if (ps->o.legacy_backends) { free_conv((conv *)ps->shadow_context); @@ -2796,8 +2710,6 @@ static void session_destroy(session_t *ps) { xrc_report_xid(); #endif - XSetErrorHandler(ps->previous_xerror_handler); - // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); @@ -2806,6 +2718,8 @@ static void session_destroy(session_t *ps) { ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); ev_signal_stop(ps->loop, &ps->int_signal); + + free_x_connection(&ps->c); } /** @@ -2872,10 +2786,9 @@ int main(int argc, char **argv) { // Failed to read, the child has most likely died // We can probably waitpid() here. return 1; - } else { - // We are done - return 0; } + // We are done + return 0; } // We are the child close(pfds[0]); diff --git a/src/picom.h b/src/picom.h index 6f14cd08ca..e68b1e042f 100644 --- a/src/picom.h +++ b/src/picom.h @@ -94,7 +94,7 @@ free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) { * Dump an drawable's info. */ static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { - auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL); + auto r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, drawable), NULL); if (!r) { log_trace("Drawable %#010x: Failed", drawable); return; diff --git a/src/render.c b/src/render.c index f4b3a97989..65d02cf335 100644 --- a/src/render.c +++ b/src/render.c @@ -48,20 +48,20 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h bool repeat, int depth, xcb_visualid_t visual, bool force) { #ifdef CONFIG_OPENGL // XXX This is a mess. But this will go away after the backend refactor. - if (!ppaint->pixmap) + if (!ppaint->pixmap) { return false; + } struct glx_fbconfig_info *fbcfg; if (!visual) { assert(depth == 32); if (!ps->argb_fbconfig) { - ps->argb_fbconfig = - glx_find_fbconfig(ps->dpy, ps->scr, - (struct xvisual_info){.red_size = 8, - .green_size = 8, - .blue_size = 8, - .alpha_size = 8, - .visual_depth = 32}); + ps->argb_fbconfig = glx_find_fbconfig( + &ps->c, (struct xvisual_info){.red_size = 8, + .green_size = 8, + .blue_size = 8, + .alpha_size = 8, + .visual_depth = 32}); } if (!ps->argb_fbconfig) { log_error("Failed to find appropriate FBConfig for 32 bit depth"); @@ -69,7 +69,7 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h } fbcfg = ps->argb_fbconfig; } else { - auto m = x_get_visual_info(ps->c, visual); + auto m = x_get_visual_info(&ps->c, visual); if (m.visual_depth < 0) { return false; } @@ -80,7 +80,7 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h } if (!ppaint->fbcfg) { - ppaint->fbcfg = glx_find_fbconfig(ps->dpy, ps->scr, m); + ppaint->fbcfg = glx_find_fbconfig(&ps->c, m); } if (!ppaint->fbcfg) { log_error("Failed to find appropriate FBConfig for X pixmap"); @@ -89,9 +89,10 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h fbcfg = ppaint->fbcfg; } - if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) + if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) { return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, repeat, fbcfg); + } #else (void)ps; (void)ppaint; @@ -129,7 +130,7 @@ static int get_buffer_age(session_t *ps) { } if (ps->o.use_damage) { unsigned int val; - glXQueryDrawable(ps->dpy, get_tgt_window(ps), + glXQueryDrawable(ps->c.dpy, get_tgt_window(ps), GLX_BACK_BUFFER_AGE_EXT, &val); return (int)val ?: -1; } @@ -144,7 +145,7 @@ static int get_buffer_age(session_t *ps) { */ static inline void xrfilter_reset(session_t *ps, xcb_render_picture_t p) { #define FILTER "Nearest" - xcb_render_set_picture_filter(ps->c, p, strlen(FILTER), FILTER, 0, NULL); + xcb_render_set_picture_filter(ps->c.c, p, strlen(FILTER), FILTER, 0, NULL); #undef FILTER } @@ -153,7 +154,7 @@ static inline void attr_nonnull(1, 2) set_tgt_clip(session_t *ps, region_t *reg) switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: - x_set_picture_clip_region(ps->c, ps->tgt_buffer.pict, 0, 0, reg); + x_set_picture_clip_region(&ps->c, ps->tgt_buffer.pict, 0, 0, reg); break; #ifdef CONFIG_OPENGL case BKEND_GLX: glx_set_clip(ps, reg); break; @@ -162,16 +163,6 @@ static inline void attr_nonnull(1, 2) set_tgt_clip(session_t *ps, region_t *reg) } } -/** - * Destroy a Picture. - */ -void free_picture(xcb_connection_t *c, xcb_render_picture_t *p) { - if (*p) { - xcb_render_free_picture(c, *p); - *p = XCB_NONE; - } -} - /** * Free paint_t. */ @@ -179,10 +170,14 @@ void free_paint(session_t *ps, paint_t *ppaint) { #ifdef CONFIG_OPENGL free_paint_glx(ps, ppaint); #endif - free_picture(ps->c, &ppaint->pict); - if (ppaint->pixmap) - xcb_free_pixmap(ps->c, ppaint->pixmap); - ppaint->pixmap = XCB_NONE; + if (ppaint->pict != XCB_NONE) { + x_free_picture(&ps->c, ppaint->pict); + ppaint->pict = XCB_NONE; + } + if (ppaint->pixmap) { + xcb_free_pixmap(ps->c.c, ppaint->pixmap); + ppaint->pixmap = XCB_NONE; + } } uint32_t @@ -253,8 +248,7 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f if (alpha_step != 0) { if (cr) { xcb_render_picture_t p_tmp = x_create_picture_with_standard( - ps->c, ps->root, fullwid, fullhei, - XCB_PICT_STANDARD_ARGB_32, 0, 0); + &ps->c, fullwid, fullhei, XCB_PICT_STANDARD_ARGB_32, 0, 0); xcb_render_color_t trans = { .red = 0, .blue = 0, .green = 0, .alpha = 0}; const xcb_rectangle_t rect = { @@ -262,7 +256,7 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f .y = 0, .width = to_u16_checked(fullwid), .height = to_u16_checked(fullhei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + xcb_render_fill_rectangles(ps->c.c, XCB_RENDER_PICT_OP_SRC, p_tmp, trans, 1, &rect); uint32_t max_ntraps = to_u32_checked(cr); @@ -272,25 +266,24 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f traps, max_ntraps, cr, fullwid, fullhei); xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + ps->c.c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(&ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, + ps->c.c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, ps->tgt_buffer.pict, to_i16_checked(x), to_i16_checked(y), to_i16_checked(x), to_i16_checked(y), to_i16_checked(dx), to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); - xcb_render_free_picture(ps->c, p_tmp); + x_free_picture(&ps->c, p_tmp); } else { xcb_render_picture_t p_tmp = alpha_pict; if (clip) { p_tmp = x_create_picture_with_standard( - ps->c, ps->root, wid, hei, - XCB_PICT_STANDARD_ARGB_32, 0, 0); + &ps->c, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); xcb_render_color_t black = { .red = 255, .blue = 255, .green = 255, .alpha = 255}; @@ -299,17 +292,18 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + xcb_render_fill_rectangles(ps->c.c, + XCB_RENDER_PICT_OP_SRC, p_tmp, black, 1, &rect); if (alpha_pict) { xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_SRC, + ps->c.c, XCB_RENDER_PICT_OP_SRC, alpha_pict, XCB_NONE, p_tmp, 0, 0, 0, 0, 0, 0, to_u16_checked(wid), to_u16_checked(hei)); } xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE, + ps->c.c, XCB_RENDER_PICT_OP_OUT_REVERSE, clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0, to_i16_checked(clip->x), to_i16_checked(clip->y), to_u16_checked(wid), to_u16_checked(hei)); @@ -319,12 +313,12 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f : XCB_RENDER_PICT_OP_OVER); xcb_render_composite( - ps->c, op, pict, p_tmp, ps->tgt_buffer.pict, + ps->c.c, op, pict, p_tmp, ps->tgt_buffer.pict, to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); if (clip) { - xcb_render_free_picture(ps->c, p_tmp); + x_free_picture(&ps->c, p_tmp); } } } @@ -375,15 +369,18 @@ paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { // Don't check for presence of Pixmap here, because older X Composite doesn't // provide it - if (!ppaint) + if (!ppaint) { return false; + } - if (bkend_use_xrender(ps) && !ppaint->pict) + if (bkend_use_xrender(ps) && !ppaint->pict) { return false; + } #ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) + if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) { return false; + } #endif return true; @@ -395,9 +392,9 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) { // Fetch Pixmap if (!w->paint.pixmap) { - w->paint.pixmap = x_new_id(ps->c); - set_ignore_cookie(ps, xcb_composite_name_window_pixmap(ps->c, w->base.id, - w->paint.pixmap)); + w->paint.pixmap = x_new_id(&ps->c); + set_ignore_cookie(&ps->c, xcb_composite_name_window_pixmap( + ps->c.c, w->base.id, w->paint.pixmap)); } xcb_drawable_t draw = w->paint.pixmap; @@ -415,7 +412,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) }; w->paint.pict = x_create_picture_with_pictfmt_and_pixmap( - ps->c, w->pictfmt, draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + &ps->c, w->pictfmt, draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); } // GLX: Build texture @@ -442,8 +439,8 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) // Invert window color, if required if (bkend_use_xrender(ps) && w->invert_color) { - xcb_render_picture_t newpict = x_create_picture_with_pictfmt( - ps->c, ps->root, wid, hei, w->pictfmt, 0, NULL); + xcb_render_picture_t newpict = + x_create_picture_with_pictfmt(&ps->c, wid, hei, w->pictfmt, 0, NULL); if (newpict) { // Apply clipping region to save some CPU if (reg_paint) { @@ -456,17 +453,18 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) pixman_region32_fini(®); } - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, pict, XCB_NONE, - newpict, 0, 0, 0, 0, 0, 0, wid, hei); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, pict, + XCB_NONE, newpict, 0, 0, 0, 0, 0, 0, wid, hei); + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_DIFFERENCE, ps->white_picture, XCB_NONE, newpict, 0, 0, 0, 0, 0, 0, wid, hei); // We use an extra PictOpInReverse operation to get correct // pixel alpha. There could be a better solution. - if (win_has_alpha(w)) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_IN_REVERSE, + if (win_has_alpha(w)) { + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_IN_REVERSE, pict, XCB_NONE, newpict, 0, 0, 0, 0, 0, 0, wid, hei); + } pict = newpict; } } @@ -494,43 +492,51 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) // ctop = checked top // Make sure top margin is smaller than height int ctop = min2(body_height, t); - if (ctop > 0) + if (ctop > 0) { COMP_BDR(0, 0, wid, ctop); + } body_height -= ctop; - if (body_height <= 0) + if (body_height <= 0) { break; + } // bottom // cbot = checked bottom // Make sure bottom margin is not too large int cbot = min2(body_height, b); - if (cbot > 0) + if (cbot > 0) { COMP_BDR(0, hei - cbot, wid, cbot); + } // Height of window exclude the margin body_height -= cbot; - if (body_height <= 0) + if (body_height <= 0) { break; + } // left int body_width = wid; int cleft = min2(body_width, l); - if (cleft > 0) + if (cleft > 0) { COMP_BDR(0, ctop, cleft, body_height); + } body_width -= cleft; - if (body_width <= 0) + if (body_width <= 0) { break; + } // right int cright = min2(body_width, r); - if (cright > 0) + if (cright > 0) { COMP_BDR(wid - cright, ctop, cright, body_height); + } body_width -= cright; - if (body_width <= 0) + if (body_width <= 0) { break; + } // body paint_region(ps, w, cleft, ctop, body_width, body_height, @@ -540,14 +546,17 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) #undef COMP_BDR - if (pict != w->paint.pict) - free_picture(ps->c, &pict); + if (pict != w->paint.pict) { + x_free_picture(&ps->c, pict); + pict = XCB_NONE; + } // Dimming the window if needed if (w->dim) { double dim_opacity = ps->o.inactive_dim; - if (!ps->o.inactive_dim_fixed) + if (!ps->o.inactive_dim_fixed) { dim_opacity *= w->opacity; + } switch (ps->o.backend) { case BKEND_XRENDER: @@ -569,7 +578,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) .height = hei, }; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER, + xcb_render_fill_rectangles(ps->c.c, XCB_RENDER_PICT_OP_OVER, ps->tgt_buffer.pict, color, 1, &rect); } break; #ifdef CONFIG_OPENGL @@ -593,15 +602,17 @@ static bool get_root_tile(session_t *ps) { ps->root_tile_fill = false; bool fill = false; - xcb_pixmap_t pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms); + xcb_pixmap_t pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms); // Make sure the pixmap we got is valid - if (pixmap && !x_validate_pixmap(ps->c, pixmap)) + if (pixmap && !x_validate_pixmap(&ps->c, pixmap)) { pixmap = XCB_NONE; + } // Create a pixmap if there isn't any if (!pixmap) { - pixmap = x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, 1, 1); + pixmap = + x_create_pixmap(&ps->c, (uint8_t)ps->c.screen_info->root_depth, 1, 1); if (pixmap == XCB_NONE) { log_error("Failed to create pixmaps for root tile."); return false; @@ -614,7 +625,7 @@ static bool get_root_tile(session_t *ps) { .repeat = true, }; ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, pixmap, XCB_RENDER_CP_REPEAT, &pa); + &ps->c, ps->c.screen_info->root_visual, pixmap, XCB_RENDER_CP_REPEAT, &pa); // Fill pixmap if needed if (fill) { @@ -627,15 +638,17 @@ static bool get_root_tile(session_t *ps) { rect.x = rect.y = 0; rect.width = rect.height = 1; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + xcb_render_fill_rectangles(ps->c.c, XCB_RENDER_PICT_OP_SRC, ps->root_tile_paint.pict, col, 1, &rect); } ps->root_tile_fill = fill; ps->root_tile_paint.pixmap = pixmap; #ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend) - return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, ps->vis, false); + if (BKEND_GLX == ps->o.backend) { + return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, + ps->c.screen_info->root_visual, false); + } #endif return true; @@ -668,16 +681,16 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE; xcb_gcontext_t gc = XCB_NONE; - shadow_image = make_shadow(ps->c, (conv *)ps->shadow_context, opacity, width, height); + shadow_image = + make_shadow(&ps->c, (conv *)ps->shadow_context, opacity, width, height); if (!shadow_image) { log_error("failed to make shadow"); return XCB_NONE; } - shadow_pixmap = - x_create_pixmap(ps->c, 8, ps->root, shadow_image->width, shadow_image->height); + shadow_pixmap = x_create_pixmap(&ps->c, 8, shadow_image->width, shadow_image->height); shadow_pixmap_argb = - x_create_pixmap(ps->c, 32, ps->root, shadow_image->width, shadow_image->height); + x_create_pixmap(&ps->c, 32, shadow_image->width, shadow_image->height); if (!shadow_pixmap || !shadow_pixmap_argb) { log_error("failed to create shadow pixmaps"); @@ -685,18 +698,18 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit } shadow_picture = x_create_picture_with_standard_and_pixmap( - ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); + &ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); shadow_picture_argb = x_create_picture_with_standard_and_pixmap( - ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); + &ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); if (!shadow_picture || !shadow_picture_argb) { goto shadow_picture_err; } - gc = x_new_id(ps->c); - xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); + gc = x_new_id(&ps->c); + xcb_create_gc(ps->c.c, gc, shadow_pixmap, 0, NULL); - xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->cshadow_picture, + xcb_image_put(ps->c.c, shadow_pixmap, gc, shadow_image, 0, 0, 0); + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, ps->cshadow_picture, shadow_picture, shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width, shadow_image->height); @@ -705,26 +718,32 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit assert(!w->shadow_paint.pict); w->shadow_paint.pict = shadow_picture_argb; - xcb_free_gc(ps->c, gc); + xcb_free_gc(ps->c.c, gc); xcb_image_destroy(shadow_image); - xcb_free_pixmap(ps->c, shadow_pixmap); - xcb_render_free_picture(ps->c, shadow_picture); + xcb_free_pixmap(ps->c.c, shadow_pixmap); + x_free_picture(&ps->c, shadow_picture); return true; shadow_picture_err: - if (shadow_image) + if (shadow_image) { xcb_image_destroy(shadow_image); - if (shadow_pixmap) - xcb_free_pixmap(ps->c, shadow_pixmap); - if (shadow_pixmap_argb) - xcb_free_pixmap(ps->c, shadow_pixmap_argb); - if (shadow_picture) - xcb_render_free_picture(ps->c, shadow_picture); - if (shadow_picture_argb) - xcb_render_free_picture(ps->c, shadow_picture_argb); - if (gc) - xcb_free_gc(ps->c, gc); + } + if (shadow_pixmap) { + xcb_free_pixmap(ps->c.c, shadow_pixmap); + } + if (shadow_pixmap_argb) { + xcb_free_pixmap(ps->c.c, shadow_pixmap_argb); + } + if (shadow_picture) { + x_free_picture(&ps->c, shadow_picture); + } + if (shadow_picture_argb) { + x_free_picture(&ps->c, shadow_picture_argb); + } + if (gc) { + xcb_free_gc(ps->c.c, gc); + } return false; } @@ -753,23 +772,22 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { traps, max_ntraps, w->corner_radius, w->widthb, w->heightb); td = x_create_picture_with_standard( - ps->c, ps->root, w->widthb, w->heightb, - XCB_PICT_STANDARD_ARGB_32, 0, 0); + &ps->c, w->widthb, w->heightb, XCB_PICT_STANDARD_ARGB_32, 0, 0); xcb_render_color_t trans = { .red = 0, .blue = 0, .green = 0, .alpha = 0}; const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(w->widthb), .height = to_u16_checked(w->heightb)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, + xcb_render_fill_rectangles(ps->c.c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); - auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + auto solid = solid_picture(&ps->c, false, 1, 0, 0, 0); xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, + ps->c.c, XCB_RENDER_PICT_OP_OVER, solid, td, + x_get_pictfmt_for_standard(&ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); - xcb_render_free_picture(ps->c, solid); + x_free_picture(&ps->c, solid); } else { // Not implemented } @@ -785,7 +803,7 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL, should_clip ? &clip : NULL); if (td) { - xcb_render_free_picture(ps->c, td); + x_free_picture(&ps->c, td); } } @@ -813,16 +831,17 @@ xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y // Directly copying from tgt_buffer to it does not work, so we create a // Picture in the middle. - xcb_render_picture_t tmp_picture = - x_create_picture_with_visual(ps->c, ps->root, wid, hei, ps->vis, 0, NULL); + xcb_render_picture_t tmp_picture = x_create_picture_with_visual( + &ps->c, wid, hei, ps->c.screen_info->root_visual, 0, NULL); if (!tmp_picture) { log_error("Failed to build intermediate Picture."); return false; } - if (reg_clip && tmp_picture) - x_set_picture_clip_region(ps->c, tmp_picture, 0, 0, reg_clip); + if (reg_clip && tmp_picture) { + x_set_picture_clip_region(&ps->c, tmp_picture, 0, 0, reg_clip); + } xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture; for (int i = 0; i < nkernels; ++i) { @@ -836,9 +855,9 @@ xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y // be applied on source picture, to get the nearby pixels outside the // window. xcb_render_set_picture_filter( - ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, + ps->c.c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, (uint32_t)(kwid * khei + 2), convolution_blur); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, dst_pict, (rd_from_tgt ? x : 0), (rd_from_tgt ? y : 0), 0, 0, (rd_from_tgt ? 0 : x), (rd_from_tgt ? 0 : y), wid, hei); @@ -851,11 +870,12 @@ xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y } } - if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, + if (src_pict != tgt_buffer) { + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); + } - free_picture(ps->c, &tmp_picture); + x_free_picture(&ps->c, tmp_picture); return true; } @@ -910,23 +930,23 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t make_rounded_window_shape(traps, max_ntraps, cr, wid, hei); td = x_create_picture_with_standard( - ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + &ps->c, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); xcb_render_color_t trans = { .red = 0, .blue = 0, .green = 0, .alpha = 0}; const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, + xcb_render_fill_rectangles(ps->c.c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); - auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + auto solid = solid_picture(&ps->c, false, 1, 0, 0, 0); xcb_render_trapezoids( - ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, - x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, + ps->c.c, XCB_RENDER_PICT_OP_OVER, solid, td, + x_get_pictfmt_for_standard(&ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); - xcb_render_free_picture(ps->c, solid); + x_free_picture(&ps->c, solid); } // Minimize the region we try to blur, if the window itself is not @@ -946,7 +966,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, ps->o.blur_kernel_count, ®_blur, td); if (td) { - xcb_render_free_picture(ps->c, td); + x_free_picture(&ps->c, td); } pixman_region32_clear(®_blur); } break; @@ -969,10 +989,10 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t /// region_real = the damage region void paint_all(session_t *ps, struct managed_win *t) { if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) { - if (ps->xsync_exists && !x_fence_sync(ps->c, ps->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); + xcb_sync_destroy_fence(ps->c.c, ps->sync_fence); ps->sync_fence = XCB_NONE; ps->o.xrender_sync_fence = false; ps->xsync_exists = false; @@ -1010,7 +1030,7 @@ void paint_all(session_t *ps, struct managed_win *t) { if (!ps->tgt_buffer.pixmap) { free_paint(ps, &ps->tgt_buffer); ps->tgt_buffer.pixmap = - x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, + x_create_pixmap(&ps->c, ps->c.screen_info->root_depth, ps->root_width, ps->root_height); if (ps->tgt_buffer.pixmap == XCB_NONE) { log_fatal("Failed to allocate a screen-sized pixmap for" @@ -1019,18 +1039,20 @@ void paint_all(session_t *ps, struct managed_win *t) { } } - if (BKEND_GLX != ps->o.backend) + if (BKEND_GLX != ps->o.backend) { ps->tgt_buffer.pict = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->tgt_buffer.pixmap, 0, 0); + &ps->c, ps->c.screen_info->root_visual, ps->tgt_buffer.pixmap, + 0, 0); + } } if (BKEND_XRENDER == ps->o.backend) { - x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, ®ion); + x_set_picture_clip_region(&ps->c, ps->tgt_picture, 0, 0, ®ion); } #ifdef CONFIG_OPENGL if (bkend_use_glx(ps)) { - ps->psglx->z = 0.0; + ps->psglx->z = 0; } #endif @@ -1066,18 +1088,21 @@ void paint_all(session_t *ps, struct managed_win *t) { // Painting shadow if (w->shadow) { // Lazy shadow building - if (!w->shadow_paint.pixmap) - if (!win_build_shadow(ps, w, 1)) + if (!w->shadow_paint.pixmap) { + if (!win_build_shadow(ps, w, 1)) { log_error("build shadow failed"); + } + } // Shadow doesn't need to be painted underneath the body // of the windows above. Because no one can see it pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); // Mask out the region we don't want shadow on - if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) + if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) { pixman_region32_subtract(®_tmp, ®_tmp, &ps->shadow_exclude_reg); + } if (pixman_region32_not_empty(®_shadow_clip)) { pixman_region32_subtract(®_tmp, ®_tmp, ®_shadow_clip); } @@ -1093,11 +1118,12 @@ void paint_all(session_t *ps, struct managed_win *t) { // needed Doing it here instead of in make_shadow() for // saving GPU power and handling shaped windows (XXX // unconfirmed) - if (!ps->o.wintype_option[w->window_type].full_shadow) + if (!ps->o.wintype_option[w->window_type].full_shadow) { pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); + } if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 && - w->randr_monitor < ps->randr_nmonitors) { + w->randr_monitor < ps->monitors.count) { // There can be a window where number of monitors is // updated, but the monitor number attached to the window // have not. @@ -1107,7 +1133,7 @@ void paint_all(session_t *ps, struct managed_win *t) { // bounds. pixman_region32_intersect( ®_tmp, ®_tmp, - &ps->randr_monitor_regs[w->randr_monitor]); + &ps->monitors.regions[w->randr_monitor]); } // Detect if the region is empty before painting @@ -1197,13 +1223,14 @@ void paint_all(session_t *ps, struct managed_win *t) { if (ps->o.vsync) { // Make sure all previous requests are processed to achieve best // effect - x_sync(ps->c); + x_sync(&ps->c); #ifdef CONFIG_OPENGL if (glx_has_context(ps)) { - if (ps->o.vsync_use_glfinish) + if (ps->o.vsync_use_glfinish) { glFinish(); - else + } else { glFlush(); + } glXWaitX(); } #endif @@ -1224,57 +1251,63 @@ void paint_all(session_t *ps, struct managed_win *t) { // First we create a new picture, and copy content from the buffer // to it - auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); + auto pictfmt = x_get_pictform_for_visual( + &ps->c, ps->c.screen_info->root_visual); xcb_render_picture_t new_pict = x_create_picture_with_pictfmt( - ps->c, ps->root, rwidth, rheight, pictfmt, 0, NULL); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, + &ps->c, rwidth, rheight, pictfmt, 0, NULL); + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, XCB_NONE, new_pict, 0, 0, 0, 0, 0, 0, rwidth, rheight); // Next, we set the region of paint and highlight it - x_set_picture_clip_region(ps->c, new_pict, 0, 0, ®ion); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, + x_set_picture_clip_region(&ps->c, new_pict, 0, 0, ®ion); + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0, 0, 0, 0, 0, 0, rwidth, rheight); // Finally, clear clip regions of new_pict and the screen, and put // the whole thing on screen - x_set_picture_clip_region(ps->c, new_pict, 0, 0, &ps->screen_reg); - x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, &ps->screen_reg); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, new_pict, + x_set_picture_clip_region(&ps->c, new_pict, 0, 0, &ps->screen_reg); + x_set_picture_clip_region(&ps->c, ps->tgt_picture, 0, 0, + &ps->screen_reg); + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, new_pict, XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0, rwidth, rheight); - xcb_render_free_picture(ps->c, new_pict); - } else - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, + x_free_picture(&ps->c, new_pict); + } else { + xcb_render_composite(ps->c.c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0, rwidth, rheight); + } break; #ifdef CONFIG_OPENGL case BKEND_XR_GLX_HYBRID: - x_sync(ps->c); - if (ps->o.vsync_use_glfinish) + x_sync(&ps->c); + if (ps->o.vsync_use_glfinish) { glFinish(); - else + } else { glFlush(); + } glXWaitX(); assert(ps->tgt_buffer.pixmap); paint_bind_tex(ps, &ps->tgt_buffer, ps->root_width, ps->root_height, - false, ps->depth, ps->vis, !ps->o.glx_no_rebind_pixmap); - if (ps->o.vsync_use_glfinish) + false, ps->c.screen_info->root_depth, + ps->c.screen_info->root_visual, !ps->o.glx_no_rebind_pixmap); + if (ps->o.vsync_use_glfinish) { glFinish(); - else + } else { glFlush(); + } glXWaitX(); glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, ps->root_height, 0, 1.0, false, false, ®ion, NULL); fallthrough(); - case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break; + case BKEND_GLX: glXSwapBuffers(ps->c.dpy, get_tgt_window(ps)); break; #endif default: assert(0); } - x_sync(ps->c); + x_sync(&ps->c); #ifdef CONFIG_OPENGL if (glx_has_context(ps)) { @@ -1304,7 +1337,7 @@ void paint_all(session_t *ps, struct managed_win *t) { static bool xr_init_blur(session_t *ps) { // Query filters xcb_render_query_filters_reply_t *pf = xcb_render_query_filters_reply( - ps->c, xcb_render_query_filters(ps->c, get_tgt_window(ps)), NULL); + ps->c.c, xcb_render_query_filters(ps->c.c, get_tgt_window(ps)), NULL); if (pf) { xcb_str_iterator_t iter = xcb_render_query_filters_filters_iterator(pf); for (; iter.rem; xcb_str_next(&iter)) { @@ -1312,8 +1345,9 @@ static bool xr_init_blur(session_t *ps) { char *name = xcb_str_name(iter.data); // Check for the convolution filter if (strlen(XRFILTER_CONVOLUTION) == len && - !memcmp(XRFILTER_CONVOLUTION, name, strlen(XRFILTER_CONVOLUTION))) + !memcmp(XRFILTER_CONVOLUTION, name, strlen(XRFILTER_CONVOLUTION))) { ps->xrfilter_convolution_exists = true; + } } free(pf); } @@ -1337,9 +1371,10 @@ static bool init_alpha_picts(session_t *ps) { for (int i = 0; i <= MAX_ALPHA; ++i) { double o = (double)i / MAX_ALPHA; - ps->alpha_picts[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0); - if (ps->alpha_picts[i] == XCB_NONE) + ps->alpha_picts[i] = solid_picture(&ps->c, false, o, 0, 0, 0); + if (ps->alpha_picts[i] == XCB_NONE) { return false; + } } return true; } @@ -1351,12 +1386,13 @@ bool init_render(session_t *ps) { // Initialize OpenGL as early as possible #ifdef CONFIG_OPENGL - glxext_init(ps->dpy, ps->scr); + glxext_init(ps->c.dpy, ps->c.screen); #endif if (bkend_use_glx(ps)) { #ifdef CONFIG_OPENGL - if (!glx_init(ps, true)) + if (!glx_init(ps, true)) { return false; + } #else log_error("GLX backend support not compiled in."); return false; @@ -1371,8 +1407,9 @@ bool init_render(session_t *ps) { // Initialize window GL shader if (BKEND_GLX == ps->o.backend && ps->o.glx_fshader_win_str) { #ifdef CONFIG_OPENGL - if (!glx_load_prog_main(NULL, ps->o.glx_fshader_win_str, &ps->glx_prog_win)) + if (!glx_load_prog_main(NULL, ps->o.glx_fshader_win_str, &ps->glx_prog_win)) { return false; + } #else log_error("GLSL supported not compiled in, can't load " "shader."); @@ -1411,8 +1448,8 @@ bool init_render(session_t *ps) { } } - ps->black_picture = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0); - ps->white_picture = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1); + ps->black_picture = solid_picture(&ps->c, true, 1, 0, 0, 0); + ps->white_picture = solid_picture(&ps->c, true, 1, 1, 1, 1); if (ps->black_picture == XCB_NONE || ps->white_picture == XCB_NONE) { log_error("Failed to create solid xrender pictures."); @@ -1424,7 +1461,7 @@ bool init_render(session_t *ps) { if (ps->o.shadow_red == 0 && ps->o.shadow_green == 0 && ps->o.shadow_blue == 0) { ps->cshadow_picture = ps->black_picture; } else { - ps->cshadow_picture = solid_picture(ps->c, ps->root, true, 1, ps->o.shadow_red, + ps->cshadow_picture = solid_picture(&ps->c, true, 1, ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue); if (ps->cshadow_picture == XCB_NONE) { log_error("Failed to create shadow picture."); @@ -1450,14 +1487,14 @@ bool init_render(session_t *ps) { * Free root tile related things. */ void free_root_tile(session_t *ps) { - free_picture(ps->c, &ps->root_tile_paint.pict); + x_free_picture(&ps->c, ps->root_tile_paint.pict); #ifdef CONFIG_OPENGL free_texture(ps, &ps->root_tile_paint.ptex); #else assert(!ps->root_tile_paint.ptex); #endif if (ps->root_tile_fill) { - xcb_free_pixmap(ps->c, ps->root_tile_paint.pixmap); + xcb_free_pixmap(ps->c.c, ps->root_tile_paint.pixmap); ps->root_tile_paint.pixmap = XCB_NONE; } ps->root_tile_paint.pixmap = XCB_NONE; @@ -1466,19 +1503,20 @@ void free_root_tile(session_t *ps) { void deinit_render(session_t *ps) { // Free alpha_picts - for (int i = 0; i <= MAX_ALPHA; ++i) - free_picture(ps->c, &ps->alpha_picts[i]); + for (int i = 0; i <= MAX_ALPHA; ++i) { + x_free_picture(&ps->c, ps->alpha_picts[i]); + } free(ps->alpha_picts); ps->alpha_picts = NULL; // Free cshadow_picture and black_picture - if (ps->cshadow_picture == ps->black_picture) - ps->cshadow_picture = XCB_NONE; - else - free_picture(ps->c, &ps->cshadow_picture); + if (ps->cshadow_picture != ps->black_picture) { + x_free_picture(&ps->c, ps->cshadow_picture); + } - free_picture(ps->c, &ps->black_picture); - free_picture(ps->c, &ps->white_picture); + x_free_picture(&ps->c, ps->black_picture); + x_free_picture(&ps->c, ps->white_picture); + ps->cshadow_picture = ps->black_picture = ps->white_picture = XCB_NONE; // Free other X resources free_root_tile(ps); diff --git a/src/render.h b/src/render.h index 249f7bf255..4e0c7a8162 100644 --- a/src/render.h +++ b/src/render.h @@ -39,8 +39,6 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); void paint_all(session_t *ps, struct managed_win *const t); -void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); - void free_paint(session_t *ps, paint_t *ppaint); void free_root_tile(session_t *ps); diff --git a/src/vsync.c b/src/vsync.c index 5980155837..57fbb595ef 100644 --- a/src/vsync.c +++ b/src/vsync.c @@ -77,31 +77,35 @@ static bool vsync_drm_init(session_t *ps) { * @return true for success, false otherwise */ static bool vsync_opengl_init(session_t *ps) { - if (!ensure_glx_context(ps)) + if (!ensure_glx_context(ps)) { return false; + } return glxext.has_GLX_SGI_video_sync; } static bool vsync_opengl_oml_init(session_t *ps) { - if (!ensure_glx_context(ps)) + if (!ensure_glx_context(ps)) { return false; + } return glxext.has_GLX_OML_sync_control; } static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) { - if (glxext.has_GLX_MESA_swap_control) + if (glxext.has_GLX_MESA_swap_control) { return glXSwapIntervalMESA((uint)interval) == 0; - else if (glxext.has_GLX_SGI_swap_control) + } + if (glxext.has_GLX_SGI_swap_control) { return glXSwapIntervalSGI(interval) == 0; - else if (glxext.has_GLX_EXT_swap_control) { + } + if (glxext.has_GLX_EXT_swap_control) { GLXDrawable d = glXGetCurrentDrawable(); if (d == None) { // We don't have a context?? return false; } - glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval); + glXSwapIntervalEXT(ps->c.dpy, glXGetCurrentDrawable(), interval); return true; } return false; @@ -140,8 +144,8 @@ static int vsync_opengl_wait(session_t *ps attr_unused) { static int vsync_opengl_oml_wait(session_t *ps) { int64_t ust = 0, msc = 0, sbc = 0; - glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc); - glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc); + glXGetSyncValuesOML(ps->c.dpy, ps->reg_win, &ust, &msc, &sbc); + glXWaitForMscOML(ps->c.dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc); return 0; } #endif diff --git a/src/win.c b/src/win.c index 1aa10fd217..14e2953f81 100644 --- a/src/win.c +++ b/src/win.c @@ -324,7 +324,7 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w assert(!w->win_image); auto pixmap = x_new_id(b->c); auto e = xcb_request_check( - b->c, xcb_composite_name_window_pixmap_checked(b->c, w->base.id, pixmap)); + b->c->c, xcb_composite_name_window_pixmap_checked(b->c->c, w->base.id, pixmap)); if (e) { log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id, w->name); @@ -519,7 +519,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { win_clear_flags(w, WIN_FLAGS_POSITION_STALE); } - win_update_monitor(ps->randr_nmonitors, ps->randr_monitor_regs, w); + win_update_monitor(&ps->monitors, w); } if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) { @@ -702,7 +702,7 @@ static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) { Bool bounding_shaped; reply = xcb_shape_query_extents_reply( - ps->c, xcb_shape_query_extents(ps->c, wid), NULL); + ps->c.c, xcb_shape_query_extents(ps->c.c, wid), NULL); bounding_shaped = reply && reply->bounding_shaped; free(reply); @@ -714,7 +714,7 @@ static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) { static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) { winprop_t prop = - x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); + x_get_prop(&ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); for (unsigned i = 0; i < prop.nitems; ++i) { for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { @@ -735,7 +735,7 @@ wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t * bool ret = false; *out = def; - winprop_t prop = x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L, + winprop_t prop = x_get_prop(&ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L, XCB_ATOM_CARDINAL, 32); if (prop.nitems) { @@ -827,11 +827,12 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) { } else { // Respect active_opacity only when the window is physically // focused - if (win_is_focused_raw(ps, w)) + if (win_is_focused_raw(ps, w)) { opacity = ps->o.active_opacity; - else if (!w->focused) + } else if (!w->focused) { // Respect inactive_opacity in some cases opacity = ps->o.inactive_opacity; + } } // respect inactive override @@ -853,9 +854,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) { if (ps->o.inactive_dim > 0 && !(w->focused)) { return true; - } else { - return false; } + return false; } /** @@ -887,7 +887,7 @@ bool win_should_fade(session_t *ps, const struct managed_win *w) { * The property must be set on the outermost window, usually the WM frame. */ void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) { - winprop_t prop = x_get_prop(ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1, + winprop_t prop = x_get_prop(&ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1, XCB_ATOM_CARDINAL, 32); if (!prop.nitems) { @@ -1085,8 +1085,9 @@ void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) { static void win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) { - if (w->blur_background == blur_background_new) + if (w->blur_background == blur_background_new) { return; + } w->blur_background = blur_background_new; @@ -1284,10 +1285,11 @@ void win_update_wintype(session_t *ps, struct managed_win *w) { // _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. if (WINTYPE_UNKNOWN == w->window_type) { if (w->a.override_redirect || - !wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) + !wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) { w->window_type = WINTYPE_NORMAL; - else + } else { w->window_type = WINTYPE_DIALOG; + } } if (w->window_type != wtype_old) { @@ -1312,9 +1314,9 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) } auto e = xcb_request_check( - ps->c, xcb_change_window_attributes_checked( - ps->c, client, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)})); + ps->c.c, xcb_change_window_attributes_checked( + ps->c.c, client, XCB_CW_EVENT_MASK, + (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)})); if (e) { log_error("Failed to change event mask of window %#010x", client); free(e); @@ -1339,13 +1341,13 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) win_on_factor_change(ps, w); auto r = xcb_get_window_attributes_reply( - ps->c, xcb_get_window_attributes(ps->c, w->client_win), &e); + ps->c.c, xcb_get_window_attributes(ps->c.c, w->client_win), &e); if (!r) { log_error_x_error(e, "Failed to get client window attributes"); return; } - w->client_pictfmt = x_get_pictform_for_visual(ps->c, r->visual); + w->client_pictfmt = x_get_pictform_for_visual(&ps->c, r->visual); free(r); } @@ -1364,7 +1366,7 @@ void win_unmark_client(session_t *ps, struct managed_win *w) { // Recheck event mask xcb_change_window_attributes( - ps->c, client, XCB_CW_EVENT_MASK, + ps->c.c, client, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)}); } @@ -1377,7 +1379,7 @@ static xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { } xcb_query_tree_reply_t *reply = - xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL); + xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, w), NULL); if (!reply) { return 0; } @@ -1451,7 +1453,7 @@ void free_win_res(session_t *ps, struct managed_win *w) { pixman_region32_fini(&w->bounding_shape); // BadDamage may be thrown if the window is destroyed - set_ignore_cookie(ps, xcb_damage_destroy(ps->c, w->damage)); + set_ignore_cookie(&ps->c, xcb_damage_destroy(ps->c.c, w->damage)); rc_region_unref(&w->reg_ignore); free(w->name); free(w->class_instance); @@ -1619,9 +1621,10 @@ struct win *fill_win(session_t *ps, struct win *w) { } log_debug("Managing window %#010x", w->id); - xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, w->id); + xcb_get_window_attributes_cookie_t acookie = + xcb_get_window_attributes(ps->c.c, w->id); xcb_get_window_attributes_reply_t *a = - xcb_get_window_attributes_reply(ps->c, acookie, NULL); + xcb_get_window_attributes_reply(ps->c.c, acookie, NULL); if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) { // Failed to get window attributes or geometry probably means // the window is gone already. Unviewable means the window is @@ -1656,7 +1659,7 @@ struct win *fill_win(session_t *ps, struct win *w) { free(a); xcb_generic_error_t *e; - auto g = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, w->id), &e); + auto g = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, w->id), &e); if (!g) { log_error_x_error(e, "Failed to get geometry of window %#010x", w->id); free(e); @@ -1674,10 +1677,10 @@ struct win *fill_win(session_t *ps, struct win *w) { free(g); // Create Damage for window (if not Input Only) - new->damage = x_new_id(ps->c); + new->damage = x_new_id(&ps->c); e = xcb_request_check( - ps->c, xcb_damage_create_checked(ps->c, new->damage, w->id, - XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); + ps->c.c, xcb_damage_create_checked(ps->c.c, new->damage, w->id, + XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); if (e) { log_error_x_error(e, "Failed to create damage"); free(e); @@ -1687,15 +1690,15 @@ struct win *fill_win(session_t *ps, struct win *w) { // Set window event mask xcb_change_window_attributes( - ps->c, new->base.id, XCB_CW_EVENT_MASK, + ps->c.c, new->base.id, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, new->base.id, WIN_EVMODE_FRAME)}); // Get notification when the shape of a window changes if (ps->shape_exists) { - xcb_shape_select_input(ps->c, new->base.id, 1); + xcb_shape_select_input(ps->c.c, new->base.id, 1); } - new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual); + new->pictfmt = x_get_pictform_for_visual(&ps->c, new->a.visual); new->client_pictfmt = NULL; list_replace(&w->stack_neighbour, &new->base.stack_neighbour); @@ -1765,12 +1768,12 @@ void win_update_leader(session_t *ps, struct managed_win *w) { // Read the leader properties if (ps->o.detect_transient && !leader) { leader = - wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); + wid_get_prop_window(&ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); } if (ps->o.detect_client_leader && !leader) { leader = - wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER); + wid_get_prop_window(&ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER); } win_set_leader(ps, w, leader); @@ -1786,8 +1789,9 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int // Rebuild the cache if needed if (!w->cache_leader && (w->client_win || w->leader)) { // Leader defaults to client window - if (!(w->cache_leader = w->leader)) + if (!(w->cache_leader = w->leader)) { w->cache_leader = w->client_win; + } // If the leader of this window isn't itself, look for its // ancestors @@ -1795,8 +1799,9 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int auto wp = find_toplevel(ps, w->cache_leader); if (wp) { // Dead loop? - if (recursions > WIN_GET_LEADER_MAX_RECURSION) + if (recursions > WIN_GET_LEADER_MAX_RECURSION) { return XCB_NONE; + } w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1); } @@ -1815,8 +1820,9 @@ bool win_update_class(session_t *ps, struct managed_win *w) { int nstr = 0; // Can't do anything if there's no client window - if (!w->client_win) + if (!w->client_win) { return false; + } // Free and reset old strings free(w->class_instance); @@ -1956,8 +1962,9 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { */ xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply( - ps->c, - xcb_shape_get_rectangles(ps->c, w->base.id, XCB_SHAPE_SK_BOUNDING), NULL); + ps->c.c, + xcb_shape_get_rectangles(ps->c.c, w->base.id, XCB_SHAPE_SK_BOUNDING), + NULL); if (!r) { break; @@ -2030,7 +2037,7 @@ void win_update_opacity_prop(session_t *ps, struct managed_win *w) { * Retrieve frame extents from a window. */ void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) { - winprop_t prop = x_get_prop(ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L, + winprop_t prop = x_get_prop(&ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L, XCB_ATOM_CARDINAL, 32); if (prop.nitems == 4) { @@ -2085,7 +2092,7 @@ bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { * Stop listening for events on a particular window. */ void win_ev_stop(session_t *ps, const struct win *w) { - xcb_change_window_attributes(ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); + xcb_change_window_attributes(ps->c.c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); if (!w->managed) { return; @@ -2093,12 +2100,12 @@ void win_ev_stop(session_t *ps, const struct win *w) { auto mw = (struct managed_win *)w; if (mw->client_win) { - xcb_change_window_attributes(ps->c, mw->client_win, XCB_CW_EVENT_MASK, + xcb_change_window_attributes(ps->c.c, mw->client_win, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); } if (ps->shape_exists) { - xcb_shape_select_input(ps->c, w->id, 0); + xcb_shape_select_input(ps->c.c, w->id, 0); } } @@ -2449,10 +2456,10 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) { // TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to // the x.c. -void win_update_monitor(int nmons, region_t *mons, struct managed_win *mw) { +void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) { mw->randr_monitor = -1; - for (int i = 0; i < nmons; i++) { - auto e = pixman_region32_extents(&mons[i]); + for (int i = 0; i < monitors->count; i++) { + auto e = pixman_region32_extents(&monitors->regions[i]); if (e->x1 <= mw->g.x && e->y1 <= mw->g.y && e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) { mw->randr_monitor = i; @@ -2670,11 +2677,12 @@ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wi // We traverse through its ancestors to find out the frame // Using find_win here because if we found a unmanaged window we know // about, we can stop early. - while (wid && wid != ps->root && !(w = find_win(ps, wid))) { + while (wid && wid != ps->c.screen_info->root && !(w = find_win(ps, wid))) { // xcb_query_tree probably fails if you run picom when X is // somehow initializing (like add it in .xinitrc). In this case // just leave it alone. - auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); + auto reply = + xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, wid), NULL); if (reply == NULL) { break; } @@ -2811,7 +2819,7 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) { */ bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { if (!ps->o.no_ewmh_fullscreen && - win_is_fullscreen_xcb(ps->c, ps->atoms, w->client_win)) { + win_is_fullscreen_xcb(ps->c.c, ps->atoms, w->client_win)) { return true; } return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && @@ -2826,7 +2834,7 @@ bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) { bool ret = false; - auto prop = x_get_prop(ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR, + auto prop = x_get_prop(&ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR, 1L, XCB_ATOM_CARDINAL, 32); if (prop.nitems && *prop.c32 == 1) { @@ -2847,13 +2855,13 @@ bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) { // Find the managed window immediately below `i` in the window stack struct managed_win * -win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { - while (!list_node_is_last(&ps->window_stack, i)) { - auto next = list_entry(i->next, struct win, stack_neighbour); +win_stack_find_next_managed(const session_t *ps, const struct list_node *w) { + while (!list_node_is_last(&ps->window_stack, w)) { + auto next = list_entry(w->next, struct win, stack_neighbour); if (next->managed) { return (struct managed_win *)next; } - i = &next->stack_neighbour; + w = &next->stack_neighbour; } return NULL; } diff --git a/src/win.h b/src/win.h index da46572b73..b841ffc177 100644 --- a/src/win.h +++ b/src/win.h @@ -341,9 +341,7 @@ void win_recheck_client(session_t *ps, struct managed_win *w); double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w); bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w); -// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to -// the x.h. -void win_update_monitor(int nmons, region_t *mons, struct managed_win *mw); +void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw); /** * Retrieve the bounding shape of a window. diff --git a/src/x.c b/src/x.c index a2a7153d74..46af709c72 100644 --- a/src/x.c +++ b/src/x.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,72 @@ #include "utils.h" #include "x.h" +// === Error handling === + +/** + * Xlib error handler function. + */ +static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { + if (!ps_g) { + // Do not ignore errors until the session has been initialized + return 0; + } + + // Fake a xcb error, fill in just enough information + xcb_generic_error_t xcb_err; + xcb_err.full_sequence = (uint32_t)ev->serial; + xcb_err.major_code = ev->request_code; + xcb_err.minor_code = ev->minor_code; + xcb_err.error_code = ev->error_code; + x_handle_error(&ps_g->c, &xcb_err); + return 0; +} + +void x_discard_pending(struct x_connection *c, uint32_t sequence) { + while (c->pending_reply_head && sequence > c->pending_reply_head->sequence) { + auto next = c->pending_reply_head->next; + free(c->pending_reply_head); + c->pending_reply_head = next; + } + if (!c->pending_reply_head) { + c->pending_reply_tail = &c->pending_reply_head; + } +} + +void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) { + x_discard_pending(c, ev->full_sequence); + if (c->pending_reply_head && c->pending_reply_head->sequence == ev->full_sequence) { + if (c->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) { + x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, + ev->minor_code, ev->error_code); + } + switch (c->pending_reply_head->action) { + case PENDING_REPLY_ACTION_ABORT: + log_fatal("An unrecoverable X error occurred, aborting..."); + abort(); + case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break; + case PENDING_REPLY_ACTION_IGNORE: break; + } + return; + } + x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code, + ev->error_code); +} + +/// Initialize x_connection struct from an Xlib Display. +/// +/// Note this function doesn't take ownership of the Display, the caller is still +/// responsible for closing it after `free_x_connection` is called. +void x_connection_init(struct x_connection *c, Display *dpy) { + c->dpy = dpy; + c->c = XGetXCBConnection(dpy); + c->pending_reply_tail = &c->pending_reply_head; + c->previous_xerror_handler = XSetErrorHandler(xerror); + + c->screen = DefaultScreen(dpy); + c->screen_info = x_screen_of_display(c, c->screen); +} + /** * Get a specific attribute of a window. * @@ -43,11 +110,11 @@ * @return a winprop_t structure containing the attribute * and number of items. A blank one on failure. */ -winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom, +winprop_t x_get_prop_with_offset(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom, int offset, int length, xcb_atom_t rtype, int rformat) { xcb_get_property_reply_t *r = xcb_get_property_reply( - c, - xcb_get_property(c, 0, w, atom, rtype, to_u32_checked(offset), + c->c, + xcb_get_property(c->c, 0, w, atom, rtype, to_u32_checked(offset), to_u32_checked(length)), NULL); @@ -71,10 +138,10 @@ winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t } /// Get the type, format and size in bytes of a window's specific attribute. -winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom) { +winprop_info_t x_get_prop_info(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom) { xcb_generic_error_t *e = NULL; auto r = xcb_get_property_reply( - c, xcb_get_property(c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e); + c->c, xcb_get_property(c->c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e); if (!r) { log_debug_x_error(e, "Failed to get property info for window %#010x", w); free(e); @@ -94,7 +161,7 @@ winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t a * * @return the value if successful, 0 otherwise */ -xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop) { +xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop) { // Get the attribute xcb_window_t p = XCB_NONE; winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32); @@ -115,7 +182,7 @@ xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, int *pnstr) { assert(ps->server_grabbed); - auto prop_info = x_get_prop_info(ps->c, wid, prop); + auto prop_info = x_get_prop_info(&ps->c, wid, prop); auto type = prop_info.type; auto format = prop_info.format; auto length = prop_info.length; @@ -140,7 +207,7 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** xcb_generic_error_t *e = NULL; auto word_count = (length + 4 - 1) / 4; auto r = xcb_get_property_reply( - ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e); + ps->c.c, xcb_get_property(ps->c.c, 0, wid, prop, type, 0, word_count), &e); if (!r) { log_debug_x_error(e, "Failed to get window property for %#010x", wid); free(e); @@ -198,14 +265,14 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** // of this program static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL; -static inline void x_get_server_pictfmts(xcb_connection_t *c) { +static inline void x_get_server_pictfmts(struct x_connection *c) { if (g_pictfmts) { return; } xcb_generic_error_t *e = NULL; // Get window picture format - g_pictfmts = - xcb_render_query_pict_formats_reply(c, xcb_render_query_pict_formats(c), &e); + g_pictfmts = xcb_render_query_pict_formats_reply( + c->c, xcb_render_query_pict_formats(c->c), &e); if (e || !g_pictfmts) { log_fatal("failed to get pict formats\n"); abort(); @@ -213,7 +280,7 @@ static inline void x_get_server_pictfmts(xcb_connection_t *c) { } const xcb_render_pictforminfo_t * -x_get_pictform_for_visual(xcb_connection_t *c, xcb_visualid_t visual) { +x_get_pictform_for_visual(struct x_connection *c, xcb_visualid_t visual) { x_get_server_pictfmts(c); xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual); @@ -244,7 +311,7 @@ static xcb_visualid_t attr_pure x_get_visual_for_pictfmt(xcb_render_query_pict_f return XCB_NONE; } -xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) { +xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std) { x_get_server_pictfmts(c); auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std); @@ -253,7 +320,7 @@ xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_ } xcb_render_pictformat_t -x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) { +x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) { x_get_server_pictfmts(c); auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std); @@ -261,8 +328,8 @@ x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) { return pictfmt->id; } -int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) { - auto setup = xcb_get_setup(c); +int x_get_visual_depth(struct x_connection *c, xcb_visualid_t visual) { + auto setup = xcb_get_setup(c->c); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); @@ -280,7 +347,7 @@ int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) { } xcb_render_picture_t -x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, +x_create_picture_with_pictfmt_and_pixmap(struct x_connection *c, const xcb_render_pictforminfo_t *pictfmt, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { @@ -294,9 +361,9 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, } xcb_render_picture_t tmp_picture = x_new_id(c); - xcb_generic_error_t *e = - xcb_request_check(c, xcb_render_create_picture_checked( - c, tmp_picture, pixmap, pictfmt->id, valuemask, buf)); + xcb_generic_error_t *e = xcb_request_check( + c->c, xcb_render_create_picture_checked(c->c, tmp_picture, pixmap, + pictfmt->id, valuemask, buf)); free(buf); if (e) { log_error_x_error(e, "failed to create picture"); @@ -307,7 +374,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, } xcb_render_picture_t -x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual, +x_create_picture_with_visual_and_pixmap(struct x_connection *c, xcb_visualid_t visual, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual); @@ -315,7 +382,7 @@ x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visu } xcb_render_picture_t -x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard, +x_create_picture_with_standard_and_pixmap(struct x_connection *c, xcb_pict_standard_t standard, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { x_get_server_pictfmts(c); @@ -326,26 +393,26 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard } xcb_render_picture_t -x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, +x_create_picture_with_standard(struct x_connection *c, int w, int h, xcb_pict_standard_t standard, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { x_get_server_pictfmts(c); auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard); assert(pictfmt); - return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr); + return x_create_picture_with_pictfmt(c, w, h, pictfmt, valuemask, attr); } /** * Create an picture. */ xcb_render_picture_t -x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h, +x_create_picture_with_pictfmt(struct x_connection *c, int w, int h, const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { uint8_t depth = pictfmt->depth; - xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h); + xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, w, h); if (!tmp_pixmap) { return XCB_NONE; } @@ -353,23 +420,23 @@ x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap( c, pictfmt, tmp_pixmap, valuemask, attr); - xcb_free_pixmap(c, tmp_pixmap); + set_cant_fail_cookie(c, xcb_free_pixmap(c->c, tmp_pixmap)); return picture; } xcb_render_picture_t -x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h, - xcb_visualid_t visual, uint32_t valuemask, +x_create_picture_with_visual(struct x_connection *c, int w, int h, xcb_visualid_t visual, + uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { auto pictfmt = x_get_pictform_for_visual(c, visual); - return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr); + return x_create_picture_with_pictfmt(c, w, h, pictfmt, valuemask, attr); } -bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_t *res) { +bool x_fetch_region(struct x_connection *c, xcb_xfixes_region_t r, pixman_region32_t *res) { xcb_generic_error_t *e = NULL; xcb_xfixes_fetch_region_reply_t *xr = - xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e); + xcb_xfixes_fetch_region_reply(c->c, xcb_xfixes_fetch_region(c->c, r), &e); if (!xr) { log_error_x_error(e, "Failed to fetch rectangles"); return false; @@ -390,7 +457,7 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_ return ret; } -uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { +uint32_t x_create_region(struct x_connection *c, const region_t *reg) { if (!reg) { return XCB_NONE; } @@ -410,8 +477,8 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { } xcb_xfixes_region_t ret = x_new_id(c); - bool success = - XCB_AWAIT_VOID(xcb_xfixes_create_region, c, ret, to_u32_checked(nrects), xrects); + bool success = XCB_AWAIT_VOID(xcb_xfixes_create_region, c->c, ret, + to_u32_checked(nrects), xrects); free(xrects); if (!success) { return XCB_NONE; @@ -419,13 +486,13 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) { return ret; } -void x_destroy_region(xcb_connection_t *c, xcb_xfixes_region_t r) { +void x_destroy_region(struct x_connection *c, xcb_xfixes_region_t r) { if (r != XCB_NONE) { - xcb_xfixes_destroy_region(c, r); + set_debug_cant_fail_cookie(c, xcb_xfixes_destroy_region(c->c, r)); } } -void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, +void x_set_picture_clip_region(struct x_connection *c, xcb_render_picture_t pict, int16_t clip_x_origin, int16_t clip_y_origin, const region_t *reg) { int nrects; @@ -440,9 +507,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, }; } - xcb_generic_error_t *e = xcb_request_check( - c, xcb_render_set_picture_clip_rectangles_checked( - c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects)); + xcb_generic_error_t *e = + xcb_request_check(c->c, xcb_render_set_picture_clip_rectangles_checked( + c->c, pict, clip_x_origin, clip_y_origin, + to_u32_checked(nrects), xrects)); if (e) { log_error_x_error(e, "Failed to set clip region"); free(e); @@ -450,17 +518,28 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, free(xrects); } -void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) { +void x_clear_picture_clip_region(struct x_connection *c, xcb_render_picture_t pict) { assert(pict != XCB_NONE); xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE}; xcb_generic_error_t *e = xcb_request_check( - c, xcb_render_change_picture_checked(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); + c->c, xcb_render_change_picture_checked(c->c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); if (e) { log_error_x_error(e, "failed to clear clip region"); free(e); } } +/** + * Destroy a Picture. + * + * Picture must be valid. + */ +void x_free_picture(struct x_connection *c, xcb_render_picture_t p) { + assert(p != XCB_NONE); + auto cookie = xcb_render_free_picture(c->c, p); + set_cant_fail_cookie(c, cookie); +} + enum { XSyncBadCounter = 0, XSyncBadAlarm = 1, @@ -593,12 +672,12 @@ const char *x_strerror(xcb_generic_error_t *e) { /** * Create a pixmap and check that creation succeeded. */ -xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable, - int width, int height) { +xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, int height) { xcb_pixmap_t pix = x_new_id(c); - xcb_void_cookie_t cookie = xcb_create_pixmap_checked( - c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height)); - xcb_generic_error_t *err = xcb_request_check(c, cookie); + xcb_void_cookie_t cookie = + xcb_create_pixmap_checked(c->c, depth, pix, c->screen_info->root, + to_u16_checked(width), to_u16_checked(height)); + xcb_generic_error_t *err = xcb_request_check(c->c, cookie); if (err == NULL) { return pix; } @@ -614,12 +693,12 @@ xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there * are better ways. */ -bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) { +bool x_validate_pixmap(struct x_connection *c, xcb_pixmap_t pixmap) { if (pixmap == XCB_NONE) { return false; } - auto r = xcb_get_geometry_reply(c, xcb_get_geometry(c, pixmap), NULL); + auto r = xcb_get_geometry_reply(c->c, xcb_get_geometry(c->c, pixmap), NULL); if (!r) { return false; } @@ -641,14 +720,14 @@ bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) { /// https://github.com/ImageMagick/ImageMagick/blob/d04a47227637dbb3af9231b0107ccf9677bf985e/MagickCore/xwindow.c#L1853-L1922 /// https://www.fvwm.org/Archive/Manpages/fvwm-root.html -xcb_pixmap_t -x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) { +xcb_pixmap_t x_get_root_back_pixmap(struct x_connection *c, struct atom *atoms) { xcb_pixmap_t pixmap = XCB_NONE; xcb_atom_t root_back_pixmap_atoms[] = {atoms->a_XROOTPMAP_ID, atoms->aESETROOT_PMAP_ID}; for (size_t i = 0; i < ARR_SIZE(root_back_pixmap_atoms); i++) { winprop_t prop = - x_get_prop(c, root, root_back_pixmap_atoms[i], 1, XCB_ATOM_PIXMAP, 32); + x_get_prop(c, c->screen_info->root, root_back_pixmap_atoms[i], 1, + XCB_ATOM_PIXMAP, 32); if (prop.nitems) { pixmap = (xcb_pixmap_t)*prop.p32; free_winprop(&prop); @@ -669,24 +748,24 @@ bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) { * Synchronizes a X Render drawable to ensure all pending painting requests * are completed. */ -bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) { +bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) { // TODO(richardgv): If everybody just follows the rules stated in X Sync // prototype, we need only one fence per screen, but let's stay a bit // cautious right now - auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f)); + auto e = xcb_request_check(c->c, xcb_sync_trigger_fence_checked(c->c, f)); if (e) { log_error_x_error(e, "Failed to trigger the fence"); goto err; } - e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f)); + e = xcb_request_check(c->c, xcb_sync_await_fence_checked(c->c, 1, &f)); if (e) { log_error_x_error(e, "Failed to await on a fence"); goto err; } - e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f)); + e = xcb_request_check(c->c, xcb_sync_reset_fence_checked(c->c, f)); if (e) { log_error_x_error(e, "Failed to reset the fence"); goto err; @@ -748,7 +827,7 @@ void x_create_convolution_kernel(const conv *kernel, double center, /// Generate a search criteria for fbconfig from a X visual. /// Returns {-1, -1, -1, -1, -1, 0} on failure -struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) { +struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual) { auto pictfmt = x_get_pictform_for_visual(c, visual); auto depth = x_get_visual_depth(c, visual); if (!pictfmt || depth == -1) { @@ -776,10 +855,10 @@ struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual }; } -xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) { +xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen) { xcb_screen_iterator_t iter; - iter = xcb_setup_roots_iterator(xcb_get_setup(c)); + iter = xcb_setup_roots_iterator(xcb_get_setup(c->c)); for (; iter.rem; --screen, xcb_screen_next(&iter)) { if (screen == 0) { return iter.data; @@ -789,39 +868,34 @@ xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) { return NULL; } -void x_update_randr_monitors(session_t *ps) { - x_free_randr_info(ps); - - if (!ps->o.crop_shadow_to_monitor || !ps->randr_exists) { - return; - } +void x_update_monitors(struct x_connection *c, struct x_monitors *m) { + x_free_monitor_info(m); xcb_randr_get_monitors_reply_t *r = xcb_randr_get_monitors_reply( - ps->c, xcb_randr_get_monitors(ps->c, ps->root, true), NULL); + c->c, xcb_randr_get_monitors(c->c, c->screen_info->root, true), NULL); if (!r) { return; } - ps->randr_nmonitors = xcb_randr_get_monitors_monitors_length(r); - ps->randr_monitor_regs = ccalloc(ps->randr_nmonitors, region_t); + m->count = xcb_randr_get_monitors_monitors_length(r); + m->regions = ccalloc(m->count, region_t); xcb_randr_monitor_info_iterator_t monitor_info_it = xcb_randr_get_monitors_monitors_iterator(r); for (int i = 0; monitor_info_it.rem; xcb_randr_monitor_info_next(&monitor_info_it)) { xcb_randr_monitor_info_t *mi = monitor_info_it.data; - pixman_region32_init_rect(&ps->randr_monitor_regs[i++], mi->x, mi->y, - mi->width, mi->height); + pixman_region32_init_rect(&m->regions[i++], mi->x, mi->y, mi->width, mi->height); } free(r); } -void x_free_randr_info(session_t *ps) { - if (ps->randr_monitor_regs) { - for (int i = 0; i < ps->randr_nmonitors; i++) { - pixman_region32_fini(&ps->randr_monitor_regs[i]); +void x_free_monitor_info(struct x_monitors *m) { + if (m->regions) { + for (int i = 0; i < m->count; i++) { + pixman_region32_fini(&m->regions[i]); } - free(ps->randr_monitor_regs); - ps->randr_monitor_regs = NULL; + free(m->regions); + m->regions = NULL; } - ps->randr_nmonitors = 0; + m->count = 0; } diff --git a/src/x.h b/src/x.h index fc105c7857..b5bd1a59d3 100644 --- a/src/x.h +++ b/src/x.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2018 Yuxuan Shui #pragma once +#include #include #include #include @@ -55,6 +56,42 @@ struct xvisual_info { xcb_visualid_t visual; }; +enum pending_reply_action { + PENDING_REPLY_ACTION_IGNORE, + PENDING_REPLY_ACTION_ABORT, + PENDING_REPLY_ACTION_DEBUG_ABORT, +}; + +typedef struct pending_reply { + struct pending_reply *next; + unsigned long sequence; + enum pending_reply_action action; +} pending_reply_t; + +struct x_connection { + /// XCB connection. + xcb_connection_t *c; + /// Display in use. + Display *dpy; + /// Head pointer of the error ignore linked list. + pending_reply_t *pending_reply_head; + /// Pointer to the next member of tail element of the error + /// ignore linked list. + pending_reply_t **pending_reply_tail; + /// Previous handler of X errors + XErrorHandler previous_xerror_handler; + /// Default screen + int screen; + /// Information about the default screen + xcb_screen_t *screen_info; +}; + +/// Monitor info +struct x_monitors { + int count; + region_t *regions; +}; + #define XCB_AWAIT_VOID(func, c, ...) \ ({ \ bool __success = true; \ @@ -92,8 +129,8 @@ struct xvisual_info { #define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) /// Wraps x_new_id. abort the program if x_new_id returns error -static inline uint32_t x_new_id(xcb_connection_t *c) { - auto ret = xcb_generate_id(c); +static inline uint32_t x_new_id(struct x_connection *c) { + auto ret = xcb_generate_id(c->c); if (ret == (uint32_t)-1) { log_fatal("We seems to have run of XIDs. This is either a bug in the X " "server, or a resource leakage in the compositor. Please open " @@ -103,6 +140,73 @@ static inline uint32_t x_new_id(xcb_connection_t *c) { return ret; } +static void set_reply_action(struct x_connection *c, uint32_t sequence, + enum pending_reply_action action) { + auto i = cmalloc(pending_reply_t); + + i->sequence = sequence; + i->next = 0; + i->action = action; + *c->pending_reply_tail = i; + c->pending_reply_tail = &i->next; +} + +/** + * Ignore X errors caused by given X request. + */ +static inline void attr_unused set_ignore_cookie(struct x_connection *c, + xcb_void_cookie_t cookie) { + set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_IGNORE); +} + +static inline void attr_unused set_cant_fail_cookie(struct x_connection *c, + xcb_void_cookie_t cookie) { + set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_ABORT); +} + +static inline void attr_unused set_debug_cant_fail_cookie(struct x_connection *c, + xcb_void_cookie_t cookie) { +#ifndef NDEBUG + set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_DEBUG_ABORT); +#else + (void)c; + (void)cookie; +#endif +} + +static inline void attr_unused free_x_connection(struct x_connection *c) { + pending_reply_t *next = NULL; + for (auto ign = c->pending_reply_head; ign; ign = next) { + next = ign->next; + + free(ign); + } + + // Reset head and tail + c->pending_reply_head = NULL; + c->pending_reply_tail = &c->pending_reply_head; + + XSetErrorHandler(c->previous_xerror_handler); +} + +/// Initialize x_connection struct from an Xlib Display. +/// +/// Note this function doesn't take ownership of the Display, the caller is still +/// responsible for closing it after `free_x_connection` is called. +void x_connection_init(struct x_connection *c, Display *dpy); + +/// Discard queued pending replies. +/// +/// We have received reply with sequence number `sequence`, which means all pending +/// replies with sequence number less than `sequence` will never be received. So discard +/// them. +void x_discard_pending(struct x_connection *c, uint32_t sequence); + +/// Handle X errors. +/// +/// This function logs X errors, or aborts the program based on severity of the error. +void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev); + /** * Send a request to X server and get the reply to make sure all previous * requests are processed, and their replies received @@ -110,8 +214,8 @@ static inline uint32_t x_new_id(xcb_connection_t *c) { * xcb_get_input_focus is used here because it is the same request used by * libX11 */ -static inline void x_sync(xcb_connection_t *c) { - free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL)); +static inline void x_sync(struct x_connection *c) { + free(xcb_get_input_focus_reply(c->c, xcb_get_input_focus(c->c), NULL)); } /** @@ -129,25 +233,26 @@ static inline void x_sync(xcb_connection_t *c) { * @return a winprop_t structure containing the attribute * and number of items. A blank one on failure. */ -winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom, +winprop_t x_get_prop_with_offset(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom, int offset, int length, xcb_atom_t rtype, int rformat); /** * Wrapper of wid_get_prop_adv(). */ -static inline winprop_t x_get_prop(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t atom, - int length, xcb_atom_t rtype, int rformat) { +static inline winprop_t +x_get_prop(const struct x_connection *c, xcb_window_t wid, xcb_atom_t atom, int length, + xcb_atom_t rtype, int rformat) { return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat); } /// Get the type, format and size in bytes of a window's specific attribute. -winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom); +winprop_info_t x_get_prop_info(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom); /// Discard all X events in queue or in flight. Should only be used when the server is /// grabbed -static inline void x_discard_events(xcb_connection_t *c) { +static inline void x_discard_events(struct x_connection *c) { xcb_generic_event_t *e; - while ((e = xcb_poll_for_event(c))) { + while ((e = xcb_poll_for_event(c->c))) { free(e); } } @@ -157,7 +262,7 @@ static inline void x_discard_events(xcb_connection_t *c) { * * @return the value if successful, 0 otherwise */ -xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop); +xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop); /** * Get the value of a text property of a window. @@ -170,30 +275,30 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** int *pnstr); const xcb_render_pictforminfo_t * -x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t); -int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t); +x_get_pictform_for_visual(struct x_connection *, xcb_visualid_t); +int x_get_visual_depth(struct x_connection *, xcb_visualid_t); xcb_render_picture_t -x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *, +x_create_picture_with_pictfmt_and_pixmap(struct x_connection *, const xcb_render_pictforminfo_t *pictfmt, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1, 2); xcb_render_picture_t -x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual, +x_create_picture_with_visual_and_pixmap(struct x_connection *, xcb_visualid_t visual, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); xcb_render_picture_t -x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard, +x_create_picture_with_standard_and_pixmap(struct x_connection *, xcb_pict_standard_t standard, xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); xcb_render_picture_t -x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, +x_create_picture_with_standard(struct x_connection *c, int w, int h, xcb_pict_standard_t standard, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); @@ -202,30 +307,37 @@ x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int * Create an picture. */ xcb_render_picture_t -x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h, +x_create_picture_with_pictfmt(struct x_connection *, int w, int h, const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) - attr_nonnull(1, 5); + attr_nonnull(1, 4); xcb_render_picture_t -x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, - xcb_visualid_t visual, uint32_t valuemask, +x_create_picture_with_visual(struct x_connection *, int w, int h, xcb_visualid_t visual, + uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); /// Fetch a X region and store it in a pixman region -bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); +bool x_fetch_region(struct x_connection *, xcb_xfixes_region_t r, region_t *res); /// Create a X region from a pixman region -uint32_t x_create_region(xcb_connection_t *c, const region_t *reg); +uint32_t x_create_region(struct x_connection *c, const region_t *reg); /// Destroy a X region -void x_destroy_region(xcb_connection_t *c, uint32_t region); +void x_destroy_region(struct x_connection *c, uint32_t region); -void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, - int16_t clip_y_origin, const region_t *); +void x_set_picture_clip_region(struct x_connection *, xcb_render_picture_t, + int16_t clip_x_origin, int16_t clip_y_origin, const region_t *); -void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); +void x_clear_picture_clip_region(struct x_connection *, xcb_render_picture_t pict); + +/** + * Destroy a Picture. + * + * Picture must be valid. + */ +void x_free_picture(struct x_connection *c, xcb_render_picture_t p); /** * Log a X11 error @@ -242,10 +354,9 @@ void x_log_error(enum log_level level, unsigned long serial, uint8_t major, */ const char *x_strerror(xcb_generic_error_t *e); -xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable, - int width, int height); +xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height); -bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap); +bool x_validate_pixmap(struct x_connection *, xcb_pixmap_t pxmap); /** * Free a winprop_t. @@ -254,22 +365,22 @@ bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap); */ static inline void free_winprop(winprop_t *pprop) { // Empty the whole structure to avoid possible issues - if (pprop->r) + if (pprop->r) { free(pprop->r); + } pprop->ptr = NULL; pprop->r = NULL; pprop->nitems = 0; } /// Get the back pixmap of the root window -xcb_pixmap_t -x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms); +xcb_pixmap_t x_get_root_back_pixmap(struct x_connection *c, struct atom *atoms); /// Return true if the atom refers to a property name that is used for the /// root window background pixmap bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom); -bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t); +bool x_fence_sync(struct x_connection *, xcb_sync_fence_t); struct x_convolution_kernel { int size; @@ -293,23 +404,18 @@ void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double c /// Generate a search criteria for fbconfig from a X visual. /// Returns {-1, -1, -1, -1, -1, -1} on failure -struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual); +struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual); -xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); +xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std); xcb_render_pictformat_t -x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); +x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std); -xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); +xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen); -/** - * X RandR-related functions. - * - * The x_update_randr_monitors function populates ps->randr_nmonitors and - * ps->randr_monitor_regs with the data X RandR provided and the - * x_free_randr_info function frees them. - */ -void x_update_randr_monitors(session_t *ps); -void x_free_randr_info(session_t *ps); +/// Populates a `struct x_monitors` with the current monitor configuration. +void x_update_monitors(struct x_connection *, struct x_monitors *); +/// Free memory allocated for a `struct x_monitors`. +void x_free_monitor_info(struct x_monitors *); uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); From 6bd780f10f32cbf0eeb05e538faf20e73a058525 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 4 Jul 2023 03:42:10 +0100 Subject: [PATCH 110/177] x: don't abort in release for double freeing a xrender picture Signed-off-by: Yuxuan Shui --- src/x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 46af709c72..1840ebb1fe 100644 --- a/src/x.c +++ b/src/x.c @@ -537,7 +537,7 @@ void x_clear_picture_clip_region(struct x_connection *c, xcb_render_picture_t pi void x_free_picture(struct x_connection *c, xcb_render_picture_t p) { assert(p != XCB_NONE); auto cookie = xcb_render_free_picture(c->c, p); - set_cant_fail_cookie(c, cookie); + set_debug_cant_fail_cookie(c, cookie); } enum { From 7a45e35ca06d019b7e83a1a211385972e8b6f738 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 4 Jul 2023 20:47:56 +0300 Subject: [PATCH 111/177] backend: gl: set usage of buffer objects to GL_STREAM_DRAW it pretty much describes how we use buffer objects now reference: https://docs.gl/gl3/glBufferData --- src/backend/gl/blur.c | 10 +++++----- src/backend/gl/gl_common.c | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index 929703816b..6c2b717edc 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -23,7 +23,7 @@ struct gl_blur_context { struct texture_size { int width; int height; - } * texture_sizes; + } *texture_sizes; /// Cached dimensions of the offscreen framebuffer. It's the same size as the /// target but is expanded in either direction by resize_width / resize_height. @@ -347,9 +347,9 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor glBindVertexArray(vao[0]); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); + indices, GL_STREAM_DRAW); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); @@ -360,10 +360,10 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor glBindBuffer(GL_ARRAY_BUFFER, bo[2]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, - coord_resized, GL_STATIC_DRAW); + coord_resized, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, - GL_STATIC_DRAW); + GL_STREAM_DRAW); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index ef9a34b89d..01a83dbbf8 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -309,9 +309,9 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im // Allocate buffers for render input GLint coord[16] = {0}; GLuint indices[] = {0, 1, 2, 2, 3, 0}; - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); + GL_STREAM_DRAW); // Do actual recursive render to 1x1 texture GLuint result_texture = _gl_average_texture_color( @@ -448,9 +448,9 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe glGenBuffers(2, bo); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); + indices, GL_STREAM_DRAW); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); @@ -1065,9 +1065,9 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img) glGenBuffers(2, bo); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); + GL_STREAM_DRAW); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); @@ -1368,9 +1368,9 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, glGenBuffers(2, bo); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); + GL_STREAM_DRAW); glEnableVertexAttribArray(vert_coord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); From 0f4cd33b35f7d0c8f1e571dc1f22b3dced31872a Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 4 Jul 2023 21:55:32 +0300 Subject: [PATCH 112/177] backend: gl: bind vertex array objects once in gl_dual_kawase_blur --- src/backend/gl/blur.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index 6c2b717edc..e0c14126a7 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -150,6 +150,9 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + glBindVertexArray(vao[1]); + int nelems = vao_nelems[1]; + for (int i = 0; i < iterations; ++i) { // Scale output width / height by half in each iteration scale_factor <<= 1; @@ -174,8 +177,6 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec assert(bctx->blur_fbos[i]); glBindTexture(GL_TEXTURE_2D, src_texture); - glBindVertexArray(vao[1]); - auto nelems = vao_nelems[1]; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); glDrawBuffer(GL_COLOR_ATTACHMENT0); @@ -206,9 +207,6 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec int tex_width = src_size.width; int tex_height = src_size.height; - // The number of indices in the selected vertex array - GLsizei nelems; - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, src_texture); glActiveTexture(GL_TEXTURE1); @@ -222,8 +220,6 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec assert(bctx->blur_fbos[i - 1]); // not last pass, draw into next framebuffer - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); glDrawBuffer(GL_COLOR_ATTACHMENT0); From 5c00ccf4d1a5ce8875d1c2bf39be8a7c6b834b86 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 4 Jul 2023 22:12:08 +0300 Subject: [PATCH 113/177] backend: gl: bind default mask texture once in gl_dual_kawase_blur and set it's uniforms once as well --- src/backend/gl/blur.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index e0c14126a7..f1900af913 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -195,6 +195,15 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, default_mask); + + glUniform1i(up_pass->uniform_mask_tex, 1); + glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); + glUniform1i(up_pass->uniform_mask_inverted, 0); + glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F); + glUniform1f(up_pass->uniform_opacity, 1.0F); + for (int i = iterations - 1; i >= 0; --i) { // Scale output width / height back by two in each iteration scale_factor >>= 1; @@ -209,21 +218,13 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, src_texture); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, default_mask); - glUniform1i(up_pass->uniform_mask_tex, 1); - glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); - glUniform1i(up_pass->uniform_mask_inverted, 0); - glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F); if (i > 0) { assert(bctx->blur_fbos[i - 1]); // not last pass, draw into next framebuffer glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(up_pass->uniform_opacity, (GLfloat)1); } else { // last pass, draw directly into the back buffer if (mask) { From e8477e0a730a33b71e29a31dd51fa1601d550735 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 6 Jul 2023 01:12:09 +0300 Subject: [PATCH 114/177] backend: fix resize factor calculation the corresponding regions need to be resized once for each window in the stack above the damaged window including the damaged window itself. we were off by one. --- src/backend/backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index f094555c33..33a3d8bcea 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -154,7 +154,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { // TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint? int resize_factor = 1; if (t) { - resize_factor = t->stacking_rank; + resize_factor = t->stacking_rank + 1; } resize_region_in_place(®_damage, blur_width * resize_factor, blur_height * resize_factor); From 8eff87f2a3b2cc17405a45273f4b7118ea43e119 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 00:11:21 +0300 Subject: [PATCH 115/177] backend: glx: address some clang-tidy issues, run clang-format --- src/backend/gl/glx.c | 2 +- src/backend/gl/glx.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index d30dc9f291..681bd17470 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -615,7 +615,7 @@ void glxext_init(Display *dpy, int screen) { #endif #undef check_ext -#define lookup(name) (name = (__typeof__(name))glXGetProcAddress((GLubyte *)#name)) +#define lookup(name) ((name) = (__typeof__(name))glXGetProcAddress((GLubyte *)#name)) // Checking if the returned function pointer is NULL is not really necessary, // or maybe not even useful, since glXGetProcAddress might always return // something. We are doing it just for completeness' sake. diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h index ce8702a974..c7ff281c8b 100644 --- a/src/backend/gl/glx.h +++ b/src/backend/gl/glx.h @@ -12,11 +12,11 @@ #undef glXBindTexImageEXT #undef glXReleaseTexImageEXT #include -#include #include +#include -#include "log.h" #include "compiler.h" +#include "log.h" #include "utils.h" #include "x.h" From dc8def0492bb5e528800af45023a7c46d4805ea2 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 00:16:31 +0300 Subject: [PATCH 116/177] backend: gl: address some clang-tidy issues --- src/backend/gl/gl_common.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 01a83dbbf8..d437fdd7bd 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -482,8 +482,6 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe glUseProgram(0); gl_check_err(); - - return; } /// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates From 599594465ce15e5117eb10c81b0f0e4fd02eb740 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 00:22:02 +0300 Subject: [PATCH 117/177] backend: address some clang-tidy issues --- src/backend/backend_common.c | 44 ++++++++++++++++++------------------ src/backend/driver.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 5b24c67a8c..3388b78516 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -405,31 +405,31 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) { int min_radius; /// Approximate gauss-blur with at least this /// radius and std-deviation } strength_levels[20] = { - {.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1 - {.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2 - {.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3 - {.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4 - {.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5 - {.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6 - {.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7 - {.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8 - {.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9 - {.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10 - {.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11 - {.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12 - {.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13 - {.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14 - {.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15 - {.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16 - {.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17 - {.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18 - {.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19 - {.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20 + {.iterations = 1, .offset = 1.25F, .min_radius = 1}, // LVL 1 + {.iterations = 1, .offset = 2.25F, .min_radius = 6}, // LVL 2 + {.iterations = 2, .offset = 2.00F, .min_radius = 11}, // LVL 3 + {.iterations = 2, .offset = 3.00F, .min_radius = 17}, // LVL 4 + {.iterations = 2, .offset = 4.25F, .min_radius = 24}, // LVL 5 + {.iterations = 3, .offset = 2.50F, .min_radius = 32}, // LVL 6 + {.iterations = 3, .offset = 3.25F, .min_radius = 40}, // LVL 7 + {.iterations = 3, .offset = 4.25F, .min_radius = 51}, // LVL 8 + {.iterations = 3, .offset = 5.50F, .min_radius = 67}, // LVL 9 + {.iterations = 4, .offset = 3.25F, .min_radius = 83}, // LVL 10 + {.iterations = 4, .offset = 4.00F, .min_radius = 101}, // LVL 11 + {.iterations = 4, .offset = 5.00F, .min_radius = 123}, // LVL 12 + {.iterations = 4, .offset = 6.00F, .min_radius = 148}, // LVL 13 + {.iterations = 4, .offset = 7.25F, .min_radius = 178}, // LVL 14 + {.iterations = 4, .offset = 8.25F, .min_radius = 208}, // LVL 15 + {.iterations = 5, .offset = 4.50F, .min_radius = 236}, // LVL 16 + {.iterations = 5, .offset = 5.25F, .min_radius = 269}, // LVL 17 + {.iterations = 5, .offset = 6.25F, .min_radius = 309}, // LVL 18 + {.iterations = 5, .offset = 7.25F, .min_radius = 357}, // LVL 19 + {.iterations = 5, .offset = 8.50F, .min_radius = 417}, // LVL 20 }; auto params = ccalloc(1, struct dual_kawase_params); params->iterations = 0; - params->offset = 1.0f; + params->offset = 1.0F; if (blur_args->strength <= 0 && blur_args->size) { // find highest level that approximates blur-strength with the selected @@ -453,7 +453,7 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) { // - Smallest texture dimensions are halved `iterations`-times // - Upsample needs pixels two-times `offset` away from the border // - Plus one for interpolation differences - params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1; + params->expand = (1 << params->iterations) * 2 * (int)ceilf(params->offset) + 1; return params; } diff --git a/src/backend/driver.h b/src/backend/driver.h index a37cda3b67..e4cc3981f1 100644 --- a/src/backend/driver.h +++ b/src/backend/driver.h @@ -47,7 +47,7 @@ static inline void print_drivers(enum driver drivers) { const char *seen_drivers[ARR_SIZE(driver_names)]; int driver_count = 0; for (size_t i = 0; i < ARR_SIZE(driver_names); i++) { - if (drivers & (1ul << i)) { + if (drivers & (1UL << i)) { seen_drivers[driver_count++] = driver_names[i]; } } From bd47a4727554816884074ba06b615079edc086c4 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 00:51:46 +0300 Subject: [PATCH 118/177] config: address some clang-tidy issues --- src/config.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/config.c b/src/config.c index 3797ffa4a1..c695433977 100644 --- a/src/config.c +++ b/src/config.c @@ -133,8 +133,9 @@ bool parse_long(const char *s, long *dest) { log_error("Invalid number: %s", s); return false; } - while (isspace((unsigned char)*endptr)) + while (isspace((unsigned char)*endptr)) { ++endptr; + } if (*endptr) { log_error("Trailing characters: %s", s); return false; @@ -216,12 +217,14 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) { // Get matrix width and height double val = 0.0; - if (src == (pc = parse_readnum(src, &val))) + if (src == (pc = parse_readnum(src, &val))) { goto err1; + } src = pc; width = (int)val; - if (src == (pc = parse_readnum(src, &val))) + if (src == (pc = parse_readnum(src, &val))) { goto err1; + } src = pc; height = (int)val; @@ -234,9 +237,10 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) { log_error("Blur kernel width/height must be odd."); goto err1; } - if (width > 16 || height > 16) + if (width > 16 || height > 16) { log_warn("Blur kernel width/height too large, may slow down" "rendering, and/or consume lots of memory"); + } // Allocate memory conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double)); @@ -362,8 +366,9 @@ struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) { *hasneg = false; for (unsigned int i = 0; i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) { - if (!strcmp(CONV_KERN_PREDEF[i].name, src)) + if (!strcmp(CONV_KERN_PREDEF[i].name, src)) { return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count); + } } int nkernels = 1; @@ -655,11 +660,13 @@ bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *incl * Add a pattern to a condition linked list. */ bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) { - if (!pattern) + if (!pattern) { return false; + } - if (!c2_parse(pcondlst, pattern, NULL)) + if (!c2_parse(pcondlst, pattern, NULL)) { exit(1); + } return true; } From 2c83fb821b172117c5d0d84095bc211157bb6bc1 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 01:04:18 +0300 Subject: [PATCH 119/177] config: drop kawase blur method it was added with intention to remove it later almost three years ago in 33c5a5a36b1ccf1da72a9e95079dcc4e3713dce9 --- src/config.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/config.c b/src/config.c index c695433977..7c041ae1a3 100644 --- a/src/config.c +++ b/src/config.c @@ -191,11 +191,6 @@ enum blur_method parse_blur_method(const char *src) { return BLUR_METHOD_GAUSSIAN; } else if (strcmp(src, "dual_kawase") == 0) { return BLUR_METHOD_DUAL_KAWASE; - } else if (strcmp(src, "kawase") == 0) { - log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. " - "Interpreted as 'dual_kawase', but this will stop working " - "soon."); - return BLUR_METHOD_DUAL_KAWASE; } else if (strcmp(src, "none") == 0) { return BLUR_METHOD_NONE; } From 4a8b937c90f6fac3315846ab32707b32f0d4115d Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 01:19:22 +0300 Subject: [PATCH 120/177] config: refactor the parse_blur_method function sort blur methods alphabetically and address some clang-tidy issues --- src/config.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/config.c b/src/config.c index 7c041ae1a3..ec39aa4390 100644 --- a/src/config.c +++ b/src/config.c @@ -183,15 +183,19 @@ const char *parse_readnum(const char *src, double *dest) { } enum blur_method parse_blur_method(const char *src) { - if (strcmp(src, "kernel") == 0) { - return BLUR_METHOD_KERNEL; - } else if (strcmp(src, "box") == 0) { + if (strcmp(src, "box") == 0) { return BLUR_METHOD_BOX; - } else if (strcmp(src, "gaussian") == 0) { - return BLUR_METHOD_GAUSSIAN; - } else if (strcmp(src, "dual_kawase") == 0) { + } + if (strcmp(src, "dual_kawase") == 0) { return BLUR_METHOD_DUAL_KAWASE; - } else if (strcmp(src, "none") == 0) { + } + if (strcmp(src, "gaussian") == 0) { + return BLUR_METHOD_GAUSSIAN; + } + if (strcmp(src, "kernel") == 0) { + return BLUR_METHOD_KERNEL; + } + if (strcmp(src, "none") == 0) { return BLUR_METHOD_NONE; } return BLUR_METHOD_INVALID; From d03cb4ea24c9340a74cc3051b34d3989768e5400 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 02:17:13 +0300 Subject: [PATCH 121/177] config_libconfig: address some clang-tidy issues, run clang-format --- src/config_libconfig.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 40bc47b838..f2a8e07faa 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -73,8 +73,9 @@ FILE *open_config_file(const char *cpath, char **ppath) { if (cpath) { FILE *ret = fopen(cpath, "r"); - if (ret && ppath) + if (ret && ppath) { *ppath = strdup(cpath); + } return ret; } @@ -124,9 +125,10 @@ void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *n // Parse an array of options if (config_setting_is_array(setting)) { int i = config_setting_length(setting); - while (i--) + while (i--) { condlst_add(pcondlst, config_setting_get_string_elem(setting, i)); + } } // Treat it as a single pattern if it's a string else if (CONFIG_TYPE_STRING == config_setting_type(setting)) { @@ -145,18 +147,22 @@ parse_cfg_condlst_corner(options_t *opt, const config_t *pcfg, const char *name) // Parse an array of options if (config_setting_is_array(setting)) { int i = config_setting_length(setting); - while (i--) + while (i--) { if (!parse_numeric_window_rule( &opt->corner_radius_rules, - config_setting_get_string_elem(setting, i), 0, INT_MAX)) + config_setting_get_string_elem(setting, i), 0, + INT_MAX)) { exit(1); + } + } } // Treat it as a single pattern if it's a string else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { if (!parse_numeric_window_rule(&opt->corner_radius_rules, config_setting_get_string(setting), - 0, INT_MAX)) + 0, INT_MAX)) { exit(1); + } } } } @@ -171,17 +177,21 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) { // Parse an array of options if (config_setting_is_array(setting)) { int i = config_setting_length(setting); - while (i--) + while (i--) { if (!parse_numeric_window_rule( &opt->opacity_rules, - config_setting_get_string_elem(setting, i), 0, 100)) + config_setting_get_string_elem(setting, i), 0, 100)) { exit(1); + } + } } // Treat it as a single pattern if it's a string else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { - if (!parse_numeric_window_rule( - &opt->opacity_rules, config_setting_get_string(setting), 0, 100)) + if (!parse_numeric_window_rule(&opt->opacity_rules, + config_setting_get_string(setting), + 0, 100)) { exit(1); + } } } } @@ -381,8 +391,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad winopt_mask[WINTYPE_POPUP_MENU].opacity = true; } // -f (fading_enable) - if (config_lookup_bool(&cfg, "fading", &ival)) + if (config_lookup_bool(&cfg, "fading", &ival)) { *fading_enable = ival; + } // --no-fading-open-close lcfg_lookup_bool(&cfg, "no-fading-openclose", &opt->no_fading_openclose); // --no-fading-destroyed-argb @@ -402,8 +413,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad opt->shadow_blue = rgb.blue; } // --shadow-exclude-reg - if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval)) + if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval)) { opt->shadow_exclude_reg_str = strdup(sval); + } // --inactive-opacity-override lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override); // --inactive-dim @@ -601,9 +613,10 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --xrender-sync-fence lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence); - if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval)) + if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval)) { log_warn("\"clear-shadow\" is removed as an option, and is always" " enabled now. Consider removing it from your config file"); + } config_setting_t *blur_cfg = config_lookup(&cfg, "blur"); if (blur_cfg) { From 6f868e54b93168fbed87b95700bdd3f0c1bfd973 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 22 Jul 2023 15:28:07 +0300 Subject: [PATCH 122/177] meson.build: sort required packages alphabetically and add a comment on why some xcb packages are in the required_packages array instead of the required_xcb_packages one --- src/meson.build | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/meson.build b/src/meson.build index ddecc64d86..a608c57538 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,12 +15,14 @@ picom_inc = include_directories('.') cflags = [] required_xcb_packages = [ - 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb-dpms', 'xcb' + 'xcb', 'xcb-composite', 'xcb-damage', 'xcb-dpms', 'xcb-glx', 'xcb-present', + 'xcb-randr', 'xcb-render', 'xcb-shape', 'xcb-sync', 'xcb-xfixes' ] +# Some XCB packages are here because their versioning differs (see check below). required_packages = [ - 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1', 'xcb-util' + 'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util', + 'xext' ] foreach i : required_packages From 7713432927b3f47eaa6d99050553ecaccfc3e47b Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 01:27:27 +0300 Subject: [PATCH 123/177] backend: xrender: don't use root picture format in backend_xrender_init use root visual and depth directly instead --- src/backend/xrender/xrender.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index bb2acd456a..c359fc4efa 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -896,12 +896,6 @@ static backend_t *backend_xrender_init(session_t *ps) { &ps->c, ps->c.screen_info->root_visual, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - auto pictfmt = x_get_pictform_for_visual(&ps->c, ps->c.screen_info->root_visual); - if (!pictfmt) { - log_fatal("Default visual is invalid"); - abort(); - } - xd->vsync = ps->o.vsync; if (ps->present_exists) { auto eid = x_new_id(&ps->c); @@ -930,14 +924,15 @@ static backend_t *backend_xrender_init(session_t *ps) { // double buffering. int first_buffer_index = xd->vsync ? 0 : 2; for (int i = first_buffer_index; i < 3; i++) { - xd->back_pixmap[i] = - x_create_pixmap(&ps->c, pictfmt->depth, to_u16_checked(ps->root_width), - to_u16_checked(ps->root_height)); + xd->back_pixmap[i] = x_create_pixmap(&ps->c, ps->c.screen_info->root_depth, + to_u16_checked(ps->root_width), + to_u16_checked(ps->root_height)); const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; const xcb_render_create_picture_value_list_t pic_attrs = { .repeat = XCB_RENDER_REPEAT_PAD}; - xd->back[i] = x_create_picture_with_pictfmt_and_pixmap( - &ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs); + xd->back[i] = x_create_picture_with_visual_and_pixmap( + &ps->c, ps->c.screen_info->root_visual, xd->back_pixmap[i], + pic_attrs_mask, &pic_attrs); xd->buffer_age[i] = -1; if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) { log_error("Cannot create pixmap for rendering"); From fd15a05166811cee71f87e6281c5939dfb080bbe Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 22:27:11 +0300 Subject: [PATCH 124/177] synchronize packages in readme with packages installed by ci some packages installed by ci were not listed (e.g. libxcb-util-dev) or different (e.g. libgl1-mesa-dev) in readme --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c0d5d0f748..0ec3050763 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libxcb-util-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libconfig-dev libdbus-1-dev libegl-dev libev-dev libevdev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index f1f66b0989..665e4b1854 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libconfig-dev libdbus-1-dev libegl-dev libev-dev libevdev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev ``` On Fedora, the needed packages are From 013c03c9a50182e67352ce737981cc077c507f80 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 22:28:56 +0300 Subject: [PATCH 125/177] don't require the libevdev-dev package in readme and install by ci it doesn't seem to be used --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0ec3050763..b1d5987dd9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libconfig-dev libdbus-1-dev libegl-dev libev-dev libevdev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev + - run: sudo apt update && sudo apt install libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index 665e4b1854..22db061850 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libconfig-dev libdbus-1-dev libegl-dev libev-dev libevdev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev +libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev ``` On Fedora, the needed packages are From 71782da245b16d7566e7818ab8b41d8a1f9057ff Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Sun, 6 Aug 2023 17:51:18 -0600 Subject: [PATCH 126/177] Add nix flake --- flake.lock | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 34 +++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..6afc8e3eb4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,80 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "git-ignore-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "ref": "master", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1691186842, + "narHash": "sha256-wxBVCvZUwq+XS4N4t9NqsHV4E64cPVqQ2fdDISpjcw0=", + "path": "/nix/store/d42v5grfq77vr10r336kks0qjp0wij8d-source", + "rev": "18036c0be90f4e308ae3ebcab0e14aae0336fe42", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "git-ignore-nix": "git-ignore-nix", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..76c0db06c1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,34 @@ +{ + inputs = { + flake-utils.url = github:numtide/flake-utils; + git-ignore-nix = { + url = github:hercules-ci/gitignore.nix/master; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + outputs = { + self, flake-utils, nixpkgs, git-ignore-nix, ... + }: flake-utils.lib.eachDefaultSystem (system: let + overlay = self: super: { + picom = super.picom.overrideAttrs (oldAttrs: rec { + pname = "picom"; + buildInputs = [ + self.pcre2 self.xorg.xcbutil + ] ++ self.lib.remove self.xorg.libXinerama ( + self.lib.remove self.pcre oldAttrs.buildInputs + ); + src = git-ignore-nix.lib.gitignoreSource ./.; + }); + }; + pkgs = import nixpkgs { inherit system overlays; config.allowBroken = true; }; + overlays = [ overlay ]; + in rec { + inherit overlay overlays; + defaultPackage = pkgs.picom; + devShell = defaultPackage.overrideAttrs { + buildInputs = defaultPackage.buildInputs ++ [ + pkgs.clang-tools + ]; + }; + }); +} From 8c29fb046bde57bbf96cf230d204dd6df8607a0d Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 00:47:14 +0300 Subject: [PATCH 127/177] region: address some clang-tidy issues, run clang-format --- src/region.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/region.h b/src/region.h index bda66e237c..770f6992a6 100644 --- a/src/region.h +++ b/src/region.h @@ -23,9 +23,10 @@ static inline void dump_region(const region_t *x) { int nrects; const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects); log_trace("nrects: %d", nrects); - for (int i = 0; i < nrects; i++) + for (int i = 0; i < nrects; i++) { log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2, rects[i].y2); + } } /// Convert one xcb rectangle to our rectangle type @@ -51,8 +52,7 @@ static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) { /** * Resize a region. */ -static inline void _resize_region(const region_t *region, region_t *output, int dx, - int dy) { +static inline void _resize_region(const region_t *region, region_t *output, int dx, int dy) { if (!region || !output) { return; } @@ -77,8 +77,7 @@ static inline void _resize_region(const region_t *region, region_t *output, int if (wid <= 0 || hei <= 0) { continue; } - newrects[nnewrects] = - (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; + newrects[nnewrects] = (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; ++nnewrects; } From b528b87cc800a18d53bc6ddb070df6658e7130f3 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 00:50:57 +0300 Subject: [PATCH 128/177] kernel: address some clang-tidy issues --- src/kernel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kernel.c b/src/kernel.c index cbb5cd12bf..b5e1a487d5 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -54,8 +54,9 @@ static inline double attr_const gaussian(double r, double x, double y) { // Formula can be found here: // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics // Except a special case for r == 0 to produce sharp shadows - if (r == 0) + if (r == 0) { return 1; + } return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r); } From 72ede901474855893b0ee70acc02820daf8b2342 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:04:58 +0300 Subject: [PATCH 129/177] dbus: address some clang-tidy issues --- src/dbus.c | 83 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/dbus.c b/src/dbus.c index ad98a0f2ae..861d424e26 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -253,8 +253,9 @@ static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data) t->t = timeout; dbus_timeout_set_data(timeout, t, NULL); - if (dbus_timeout_get_enabled(timeout)) + if (dbus_timeout_get_enabled(timeout)) { ev_timer_start(ps->loop, &t->w); + } return true; } @@ -302,10 +303,12 @@ typedef struct ev_dbus_io { void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) { ev_dbus_io *dw = (void *)w; DBusWatchFlags flags = 0; - if (revents & EV_READ) + if (revents & EV_READ) { flags |= DBUS_WATCH_READABLE; - if (revents & EV_WRITE) + } + if (revents & EV_WRITE) { flags |= DBUS_WATCH_WRITABLE; + } dbus_watch_handle(dw->dw, flags); while (dbus_connection_dispatch(dw->cd->dbus_conn) != DBUS_DISPATCH_COMPLETE) ; @@ -317,10 +320,12 @@ void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) { static inline int cdbus_get_watch_cond(DBusWatch *watch) { const unsigned flags = dbus_watch_get_flags(watch); int condition = 0; - if (flags & DBUS_WATCH_READABLE) + if (flags & DBUS_WATCH_READABLE) { condition |= EV_READ; - if (flags & DBUS_WATCH_WRITABLE) + } + if (flags & DBUS_WATCH_WRITABLE) { condition |= EV_WRITE; + } return condition; } @@ -338,8 +343,9 @@ static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data) { cdbus_get_watch_cond(watch)); // Leave disabled watches alone - if (dbus_watch_get_enabled(watch)) + if (dbus_watch_get_enabled(watch)) { ev_io_start(ps->loop, &w->w); + } dbus_watch_set_data(watch, w, NULL); @@ -363,10 +369,11 @@ static void cdbus_callback_remove_watch(DBusWatch *watch, void *data) { static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data) { session_t *ps = data; ev_io *w = dbus_watch_get_data(watch); - if (dbus_watch_get_enabled(watch)) + if (dbus_watch_get_enabled(watch)) { ev_io_start(ps->loop, w); - else + } else { ev_io_stop(ps->loop, w); + } } ///@} @@ -513,8 +520,9 @@ static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const static bool cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *data) { const char *str = data; - if (!str) + if (!str) { str = ""; + } if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) { log_error("Failed to append argument."); @@ -1050,32 +1058,36 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) { if (!strcmp("shadow_force", target)) { cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) { return false; + } win_set_shadow_force(ps, w, val); goto cdbus_process_win_set_success; } if (!strcmp("fade_force", target)) { cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) { return false; + } win_set_fade_force(w, val); goto cdbus_process_win_set_success; } if (!strcmp("focused_force", target)) { cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) { return false; + } win_set_focused_force(ps, w, val); goto cdbus_process_win_set_success; } if (!strcmp("invert_color_force", target)) { cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) + if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) { return false; + } win_set_invert_color_force(ps, w, val); goto cdbus_process_win_set_success; } @@ -1087,8 +1099,9 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) { return true; cdbus_process_win_set_success: - if (!dbus_message_get_no_reply(msg)) + if (!dbus_message_get_no_reply(msg)) { cdbus_reply_bool(ps, msg, true); + } return true; } @@ -1098,16 +1111,18 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) { static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) { const char *target = NULL; - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) { return false; + } xcb_window_t wid = XCB_NONE; // Find window by client window if (!strcmp("client", target)) { cdbus_window_t client = XCB_NONE; - if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) + if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) { return false; + } auto w = find_toplevel(ps, client); if (w) { wid = w->base.id; @@ -1136,8 +1151,9 @@ static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) { static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { const char *target = NULL; - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) { return false; + } #define cdbus_m_opts_get_do(tgt, apdarg_func) \ if (!strcmp(#tgt, target)) { \ @@ -1246,8 +1262,9 @@ void queue_redraw(session_t *ps); static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { const char *target = NULL; - if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) + if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) { return false; + } #define cdbus_m_opts_set_do(tgt, type, real_type) \ if (!strcmp(#tgt, target)) { \ @@ -1274,8 +1291,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // fade_in_step if (!strcmp("fade_in_step", target)) { double val = 0.0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) { return false; + } ps->o.fade_in_step = normalize_d(val); goto cdbus_process_opts_set_success; } @@ -1283,8 +1301,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // fade_out_step if (!strcmp("fade_out_step", target)) { double val = 0.0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) { return false; + } ps->o.fade_out_step = normalize_d(val); goto cdbus_process_opts_set_success; } @@ -1292,8 +1311,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // no_fading_openclose if (!strcmp("no_fading_openclose", target)) { dbus_bool_t val = FALSE; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) { return false; + } opts_set_no_fading_openclose(ps, val); goto cdbus_process_opts_set_success; } @@ -1301,8 +1321,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // unredir_if_possible if (!strcmp("unredir_if_possible", target)) { dbus_bool_t val = FALSE; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) { return false; + } if (ps->o.unredir_if_possible != val) { ps->o.unredir_if_possible = val; queue_redraw(ps); @@ -1323,8 +1344,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // redirected_force if (!strcmp("redirected_force", target)) { cdbus_enum_t val = UNSET; - if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val)) + if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val)) { return false; + } ps->o.redirected_force = val; force_repaint(ps); goto cdbus_process_opts_set_success; @@ -1341,8 +1363,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { return true; cdbus_process_opts_set_success: - if (!dbus_message_get_no_reply(msg)) + if (!dbus_message_get_no_reply(msg)) { cdbus_reply_bool(ps, msg, true); + } return true; } @@ -1512,13 +1535,15 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { if (cdbus_m_ismethod("reset")) { log_info("picom is resetting..."); ev_break(ps->loop, EVBREAK_ALL); - if (!dbus_message_get_no_reply(msg)) + if (!dbus_message_get_no_reply(msg)) { cdbus_reply_bool(ps, msg, true); + } handled = true; } else if (cdbus_m_ismethod("repaint")) { force_repaint(ps); - if (!dbus_message_get_no_reply(msg)) + if (!dbus_message_get_no_reply(msg)) { cdbus_reply_bool(ps, msg, true); + } handled = true; } else if (cdbus_m_ismethod("list_win")) { handled = cdbus_process_list_win(ps, msg); @@ -1566,8 +1591,9 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) { dbus_message_get_member(msg)); } if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && - !dbus_message_get_no_reply(msg)) + !dbus_message_get_no_reply(msg)) { cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + } handled = true; } @@ -1593,8 +1619,9 @@ cdbus_process_windows(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) const char *last_segment = strrchr(path, '/'); if (last_segment == NULL) { if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) && - !dbus_message_get_no_reply(msg)) + !dbus_message_get_no_reply(msg)) { cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S); + } return DBUS_HANDLER_RESULT_HANDLED; } bool is_root = strncmp(last_segment, "/windows", 8) == 0; From c8627989ada8d67a9356326d9e7cd542f65b999c Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:09:56 +0300 Subject: [PATCH 130/177] win: address some clang-tidy issues, run clang-format --- src/win.c | 12 ++++++------ src/win.h | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/win.c b/src/win.c index 14e2953f81..bd19b3d412 100644 --- a/src/win.c +++ b/src/win.c @@ -369,7 +369,8 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); } else { if (!w->mask_image) { - // It's possible we already allocated a mask because of background blur + // It's possible we already allocated a mask because of background + // blur win_bind_mask(b, w); } w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c); @@ -1502,12 +1503,11 @@ struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { return NULL; } return add_win_top(ps, id); - } else { - // we found something from the hash table, so if the stack is - // empty, we are in an inconsistent state. - assert(!list_is_empty(&ps->window_stack)); - return add_win(ps, id, w->stack_neighbour.prev); } + // we found something from the hash table, so if the stack is + // empty, we are in an inconsistent state. + assert(!list_is_empty(&ps->window_stack)); + return add_win(ps, id, w->stack_neighbour.prev); } /// Query the Xorg for information about window `win` diff --git a/src/win.h b/src/win.h index b841ffc177..51e7f9d32c 100644 --- a/src/win.h +++ b/src/win.h @@ -26,11 +26,12 @@ typedef struct session session_t; typedef struct _glx_texture glx_texture_t; #define win_stack_foreach_managed(w, win_stack) \ - list_foreach(struct managed_win, w, win_stack, base.stack_neighbour) if (w->base.managed) + list_foreach(struct managed_win, w, win_stack, \ + base.stack_neighbour) if ((w)->base.managed) #define win_stack_foreach_managed_safe(w, win_stack) \ list_foreach_safe(struct managed_win, w, win_stack, \ - base.stack_neighbour) if (w->base.managed) + base.stack_neighbour) if ((w)->base.managed) #ifdef CONFIG_OPENGL // FIXME this type should be in opengl.h From ae3c77b1a546ed9f108bd7e4edcdad4fcf5e9b55 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:15:20 +0300 Subject: [PATCH 131/177] render: address some clang-tidy issues --- src/render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render.c b/src/render.c index 65d02cf335..3a7e844842 100644 --- a/src/render.c +++ b/src/render.c @@ -973,7 +973,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t #ifdef CONFIG_OPENGL case BKEND_GLX: // TODO(compton) Handle frame opacity - glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5f, + glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5F, (float)factor_center, reg_paint, &w->glx_blur_cache); break; #endif From 34024092d70666280d4e359e4ab8a0d1160028c3 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:38:22 +0300 Subject: [PATCH 132/177] picom: address some clang-tidy issues --- src/picom.c | 3 ++- src/picom.h | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index 1567ab8962..bf18523f9f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -501,8 +501,9 @@ static void rebuild_screen_reg(session_t *ps) { */ static void rebuild_shadow_exclude_reg(session_t *ps) { bool ret = parse_geometry(ps, ps->o.shadow_exclude_reg_str, &ps->shadow_exclude_reg); - if (!ret) + if (!ret) { exit(1); + } } /// Free up all the images and deinit the backend diff --git a/src/picom.h b/src/picom.h index e68b1e042f..d42ae04208 100644 --- a/src/picom.h +++ b/src/picom.h @@ -60,9 +60,11 @@ uint8_t session_redirection_mode(session_t *ps); static inline void wintype_arr_enable_unset(switch_t arr[]) { wintype_t i; - for (i = 0; i < NUM_WINTYPES; ++i) - if (UNSET == arr[i]) + for (i = 0; i < NUM_WINTYPES; ++i) { + if (UNSET == arr[i]) { arr[i] = ON; + } + } } /** From f773e723be884d421f1fbb48fb38c3eb20c7ec06 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:40:47 +0300 Subject: [PATCH 133/177] options: address some clang-tidy issues --- src/options.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/options.c b/src/options.c index 2b0bb1771e..834b66f0a9 100644 --- a/src/options.c +++ b/src/options.c @@ -561,8 +561,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 290: // --backend opt->backend = parse_backend(optarg); - if (opt->backend >= NUM_BKEND) + if (opt->backend >= NUM_BKEND) { exit(1); + } break; P_CASEBOOL(291, glx_no_stencil); P_CASEINT(293, benchmark); @@ -622,8 +623,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; case 304: // --opacity-rule - if (!parse_numeric_window_rule(&opt->opacity_rules, optarg, 0, 100)) + if (!parse_numeric_window_rule(&opt->opacity_rules, optarg, 0, 100)) { exit(1); + } break; case 305: // --shadow-exclude-reg @@ -728,8 +730,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; case 340: // --corner-radius-rules - if (!parse_numeric_window_rule(&opt->corner_radius_rules, optarg, 0, INT_MAX)) + if (!parse_numeric_window_rule(&opt->corner_radius_rules, optarg, 0, INT_MAX)) { exit(1); + } break; case 335: // --clip-shadow-above From 8cc5090a6c3b04db74f41d1108bd19d2592fa9ad Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 11 Aug 2023 01:47:43 +0300 Subject: [PATCH 134/177] string_utils: address some clang-tidy issues --- src/string_utils.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/string_utils.h b/src/string_utils.h index 461173a346..d4781e2d9a 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -24,8 +24,9 @@ static inline int uitostr(unsigned int n, char *buf) { ret++; } - if (ret == 0) + if (ret == 0) { ret = 1; + } int pos = ret; while (pos--) { @@ -36,18 +37,22 @@ static inline int uitostr(unsigned int n, char *buf) { } static inline const char *skip_space_const(const char *src) { - if (!src) + if (!src) { return NULL; - while (*src && isspace((unsigned char)*src)) + } + while (*src && isspace((unsigned char)*src)) { src++; + } return src; } static inline char *skip_space_mut(char *src) { - if (!src) + if (!src) { return NULL; - while (*src && isspace((unsigned char)*src)) + } + while (*src && isspace((unsigned char)*src)) { src++; + } return src; } From c6abb4e270cc84b93b7dc6c5d25de4c43e2d4e10 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 15 Sep 2023 00:20:59 +0300 Subject: [PATCH 135/177] picom.sample.conf: remove the dbus option duplicate closes #1128 --- picom.sample.conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/picom.sample.conf b/picom.sample.conf index 5d02e7d735..641db57d65 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -225,9 +225,6 @@ dithered-present = false; # vsync = false vsync = true; -# Enable remote control via D-Bus. See the *D-BUS API* section below for more details. -# dbus = false - # Try to detect WM windows (a non-override-redirect window with no # child that has 'WM_STATE') and mark them as active. # From 0cd72bf61a79048e4ddeb8940666cdddb0c13304 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 15 Sep 2023 00:43:15 +0300 Subject: [PATCH 136/177] opengl: address some clang-tidy issues --- src/opengl.c | 111 ++++++++++++++++++++++++++++++++------------------- src/opengl.h | 3 +- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/src/opengl.c b/src/opengl.c index 6f4d0471fb..4031e41bd5 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -78,8 +78,9 @@ bool glx_init(session_t *ps, bool need_render) { } // Ensure GLX_EXT_texture_from_pixmap exists - if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap) + if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap) { goto glx_init_end; + } // Initialize GLX data structure if (!ps->psglx) { @@ -178,9 +179,10 @@ bool glx_init(session_t *ps, bool need_render) { // Check GL_ARB_texture_non_power_of_two, requires a GLX context and // must precede FBConfig fetching - if (need_render) + if (need_render) { psglx->has_texture_non_power_of_two = gl_has_extension("GL_ARB_texture_non_power_of_two"); + } // Render preparations if (need_render) { @@ -200,7 +202,7 @@ bool glx_init(session_t *ps, bool need_render) { } // Clear screen - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearColor(0.0F, 0.0F, 0.0F, 1.0F); // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // glXSwapBuffers(ps->c.dpy, get_tgt_window(ps)); } @@ -210,15 +212,17 @@ bool glx_init(session_t *ps, bool need_render) { glx_init_end: XFree(pvis); - if (!success) + if (!success) { glx_destroy(ps); + } return success; } static void glx_free_prog_main(glx_prog_main_t *pprogram) { - if (!pprogram) + if (!pprogram) { return; + } if (pprogram->prog) { glDeleteProgram(pprogram->prog); pprogram->prog = 0; @@ -232,8 +236,9 @@ static void glx_free_prog_main(glx_prog_main_t *pprogram) { * Destroy GLX related resources. */ void glx_destroy(session_t *ps) { - if (!ps->psglx) + if (!ps->psglx) { return; + } // Free all GLX resources of windows win_stack_foreach_managed(w, &ps->window_stack) { @@ -373,8 +378,9 @@ bool glx_init_blur(session_t *ps) { double sum = 0.0; for (int j = 0; j < height; ++j) { for (int k = 0; k < width; ++k) { - if (height / 2 == j && width / 2 == k) + if (height / 2 == j && width / 2 == k) { continue; + } double val = kern->data[j * width + k]; if (val == 0) { continue; @@ -691,8 +697,9 @@ bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, int x, i */ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, int height, bool repeat, const struct glx_fbconfig_info *fbcfg) { - if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) + if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) { return true; + } if (!pixmap) { log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap); @@ -754,14 +761,15 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, // pixmap-specific parameters, and this may change in the future GLenum tex_tgt = 0; if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts && - ps->psglx->has_texture_non_power_of_two) + ps->psglx->has_texture_non_power_of_two) { tex_tgt = GLX_TEXTURE_2D_EXT; - else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts) + } else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts) { tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; - else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts)) + } else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts)) { tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; - else + } else { tex_tgt = GLX_TEXTURE_2D_EXT; + } log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt, (GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt)); @@ -820,8 +828,9 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, // The specification requires rebinding whenever the content changes... // We can't follow this, too slow. - if (need_release) + if (need_release) { glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); + } glXBindTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); @@ -859,14 +868,16 @@ void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { */ void glx_set_clip(session_t *ps, const region_t *reg) { // Quit if we aren't using stencils - if (ps->o.glx_no_stencil) + if (ps->o.glx_no_stencil) { return; + } glDisable(GL_STENCIL_TEST); glDisable(GL_SCISSOR_TEST); - if (!reg) + if (!reg) { return; + } int nrects; const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); @@ -914,8 +925,9 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, // Calculate copy region size glx_blur_cache_t ibc = {.width = 0, .height = 0}; - if (!pbc) + if (!pbc) { pbc = &ibc; + } int mdx = dx, mdy = dy, mwidth = width, mheight = height; // log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); @@ -942,24 +954,29 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, */ GLenum tex_tgt = GL_TEXTURE_RECTANGLE; - if (ps->psglx->has_texture_non_power_of_two) + if (ps->psglx->has_texture_non_power_of_two) { tex_tgt = GL_TEXTURE_2D; + } // Free textures if size inconsistency discovered - if (mwidth != pbc->width || mheight != pbc->height) + if (mwidth != pbc->width || mheight != pbc->height) { free_glx_bc_resize(ps, pbc); + } // Generate FBO and textures if needed - if (!pbc->textures[0]) + if (!pbc->textures[0]) { pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight); + } GLuint tex_scr = pbc->textures[0]; - if (more_passes && !pbc->textures[1]) + if (more_passes && !pbc->textures[1]) { pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight); + } pbc->width = mwidth; pbc->height = mheight; GLuint tex_scr2 = pbc->textures[1]; - if (more_passes && !pbc->fbo) + if (more_passes && !pbc->fbo) { glGenFramebuffers(1, &pbc->fbo); + } const GLuint fbo = pbc->fbo; if (!tex_scr || (more_passes && !tex_scr2)) { @@ -987,7 +1004,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, } */ // Texture scaling factor - GLfloat texfac_x = 1.0f, texfac_y = 1.0f; + GLfloat texfac_x = 1.0F, texfac_y = 1.0F; if (tex_tgt == GL_TEXTURE_2D) { texfac_x /= (GLfloat)mwidth; texfac_y /= (GLfloat)mheight; @@ -1020,10 +1037,12 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, } else { glBindFramebuffer(GL_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); - if (have_scissors) + if (have_scissors) { glEnable(GL_SCISSOR_TEST); - if (have_stencil) + } + if (have_stencil) { glEnable(GL_STENCIL_TEST); + } } // Color negation for testing... @@ -1033,12 +1052,15 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glUseProgram(ppass->prog); - if (ppass->unifm_offset_x >= 0) + if (ppass->unifm_offset_x >= 0) { glUniform1f(ppass->unifm_offset_x, texfac_x); - if (ppass->unifm_offset_y >= 0) + } + if (ppass->unifm_offset_y >= 0) { glUniform1f(ppass->unifm_offset_y, texfac_y); - if (ppass->unifm_factor_center >= 0) + } + if (ppass->unifm_factor_center >= 0) { glUniform1f(ppass->unifm_factor_center, factor_center); + } P_PAINTREG_START(crect) { auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x; @@ -1088,10 +1110,12 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(tex_tgt, 0); glDisable(tex_tgt); - if (have_scissors) + if (have_scissors) { glEnable(GL_SCISSOR_TEST); - if (have_stencil) + } + if (have_stencil) { glEnable(GL_STENCIL_TEST); + } if (&ibc == pbc) { free_glx_bc(ps, pbc); @@ -1276,7 +1300,7 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, // considering all those mess in color negation and modulation glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.0f, 0.0f, 0.0f, factor); + glColor4f(0.0F, 0.0F, 0.0F, factor); P_PAINTREG_START(crect) { // XXX what does all of these variables mean? @@ -1292,7 +1316,7 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, } P_PAINTREG_END(); - glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + glColor4f(0.0F, 0.0F, 0.0F, 0.0F); glDisable(GL_BLEND); gl_check_err(); @@ -1412,15 +1436,19 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, glUseProgram(pprogram->prog); struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - if (pprogram->unifm_opacity >= 0) + if (pprogram->unifm_opacity >= 0) { glUniform1f(pprogram->unifm_opacity, (float)opacity); - if (pprogram->unifm_invert_color >= 0) + } + if (pprogram->unifm_invert_color >= 0) { glUniform1i(pprogram->unifm_invert_color, neg); - if (pprogram->unifm_tex >= 0) + } + if (pprogram->unifm_tex >= 0) { glUniform1i(pprogram->unifm_tex, 0); - if (pprogram->unifm_time >= 0) - glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0f + - (float)ts.tv_nsec / 1.0e6f); + } + if (pprogram->unifm_time >= 0) { + glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0F + + (float)ts.tv_nsec / 1.0e6F); + } } // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height, @@ -1460,8 +1488,8 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, // Invert Y if needed, this may not work as expected, though. I // don't have such a FBConfig to test with. if (!ptex->y_inverted) { - ry = 1.0f - ry; - rye = 1.0f - rye; + ry = 1.0F - ry; + rye = 1.0F - rye; } // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx, @@ -1493,7 +1521,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, // Cleanup glBindTexture(ptex->target, 0); - glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + glColor4f(0.0F, 0.0F, 0.0F, 0.0F); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisable(GL_BLEND); glDisable(GL_COLOR_LOGIC_OP); @@ -1506,8 +1534,9 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, glActiveTexture(GL_TEXTURE0); } - if (has_prog) + if (has_prog) { glUseProgram(0); + } gl_check_err(); diff --git a/src/opengl.h b/src/opengl.h index d0d37dd32a..affad2499a 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -156,8 +156,9 @@ static inline bool glx_has_context(session_t *ps) { */ static inline bool ensure_glx_context(session_t *ps) { // Create GLX context - if (!glx_has_context(ps)) + if (!glx_has_context(ps)) { glx_init(ps, false); + } return glx_has_context(ps); } From a9914cda113ccfa326046e47603860c6c333386e Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 15 Sep 2023 00:47:41 +0300 Subject: [PATCH 137/177] log: address some clang-tidy issues, run clang-format --- src/log.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/log.c b/src/log.c index 8a494b9e9d..870d755750 100644 --- a/src/log.c +++ b/src/log.c @@ -78,16 +78,21 @@ static attr_const const char *log_level_to_string(enum log_level level) { } enum log_level string_to_log_level(const char *str) { - if (strcasecmp(str, "TRACE") == 0) + if (strcasecmp(str, "TRACE") == 0) { return LOG_LEVEL_TRACE; - else if (strcasecmp(str, "DEBUG") == 0) + } + if (strcasecmp(str, "DEBUG") == 0) { return LOG_LEVEL_DEBUG; - else if (strcasecmp(str, "INFO") == 0) + } + if (strcasecmp(str, "INFO") == 0) { return LOG_LEVEL_INFO; - else if (strcasecmp(str, "WARN") == 0) + } + if (strcasecmp(str, "WARN") == 0) { return LOG_LEVEL_WARN; - else if (strcasecmp(str, "ERROR") == 0) + } + if (strcasecmp(str, "ERROR") == 0) { return LOG_LEVEL_ERROR; + } return LOG_LEVEL_INVALID; } @@ -143,8 +148,9 @@ enum log_level log_get_level(const struct log *l) { attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, const char *fmt, ...) { assert(level <= LOG_LEVEL_FATAL && level >= 0); - if (level < l->log_level) + if (level < l->log_level) { return; + } char *buf = NULL; va_list args; @@ -340,7 +346,7 @@ static void gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { auto g = (struct gl_string_marker_logger *)tgt; // strip newlines at the end of the string - while (len > 0 && str[len-1] == '\n') { + while (len > 0 && str[len - 1] == '\n') { len--; } g->gl_string_marker((GLsizei)len, str); @@ -358,8 +364,9 @@ struct log_target *gl_string_marker_logger_new(void) { } void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY"); - if (!fnptr) + if (!fnptr) { return NULL; + } auto ret = cmalloc(struct gl_string_marker_logger); ret->tgt.ops = &gl_string_marker_logger_ops; From fbc803b9839c19a65c6c46421147c29b46f446fb Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 15 Sep 2023 00:57:33 +0300 Subject: [PATCH 138/177] c2: address some clang-tidy issues, run clang-format --- src/c2.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/c2.c b/src/c2.c index 823d30ea7c..d82448aa24 100644 --- a/src/c2.c +++ b/src/c2.c @@ -233,14 +233,16 @@ static inline long winprop_get_int(winprop_t prop, size_t index) { */ static inline int strcmp_wd(const char *needle, const char *src) { int ret = mstrncmp(needle, src); - if (ret) + if (ret) { return ret; + } char c = src[strlen(needle)]; - if (isalnum((unsigned char)c) || '_' == c) + if (isalnum((unsigned char)c) || '_' == c) { return 1; - else - return 0; + } + + return 0; } /** @@ -254,8 +256,9 @@ static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) { * Reset a c2_ptr_t. */ static inline void c2_ptr_reset(c2_ptr_t *pp) { - if (pp) + if (pp) { memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t)); + } } /** @@ -988,7 +991,7 @@ static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { // Determine the pattern target #define TGTFILL(pdefid) \ - (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \ + (pleaf->predef = (pdefid), pleaf->type = C2_PREDEFS[pdefid].type, \ pleaf->format = C2_PREDEFS[pdefid].format) switch (pattern[offset]) { case 'n': TGTFILL(C2_L_PNAME); break; From 751f30578e352f6cd37fc63cbf4643e47ab3587a Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sat, 23 Sep 2023 00:36:19 +0300 Subject: [PATCH 139/177] readme: update the discord badge's link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22db061850..57eb5a4cb0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ picom [![circleci](https://circleci.com/gh/yshui/picom.svg?style=shield)](https://circleci.com/gh/yshui/picom) [![codecov](https://codecov.io/gh/yshui/picom/branch/next/graph/badge.svg?token=NRSegi0Gze)](https://codecov.io/gh/yshui/picom) -[![chat on discord](https://img.shields.io/discord/1106224720833159198?logo=discord)](https://discord.gg/uqmNX6dR) +[![chat on discord](https://img.shields.io/discord/1106224720833159198?logo=discord)](https://discord.gg/SY5JJzPgME) __picom__ is a compositor for X, and a [fork of Compton](History.md). From 249f6818577287ccaf026b3b17da36c8a1331f24 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 00:48:57 +0300 Subject: [PATCH 140/177] backend: make target window a parameter of the init backend operation to address a todo --- src/backend/backend.h | 6 +----- src/backend/dummy/dummy.c | 3 ++- src/backend/gl/egl.c | 4 ++-- src/backend/gl/glx.c | 4 ++-- src/backend/xrender/xrender.c | 4 ++-- src/diagnostic.c | 2 +- src/picom.c | 3 ++- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index 3ed7a761a8..fd3212f1f6 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -119,11 +119,7 @@ struct backend_operations { // =========== Initialization =========== /// Initialize the backend, prepare for rendering to the target window. - /// Here is how you should choose target window: - /// 1) if ps->overlay is not XCB_NONE, use that - /// 2) use ps->root otherwise - // TODO(yshui) make the target window a parameter - backend_t *(*init)(session_t *)attr_nonnull(1); + backend_t *(*init)(session_t *, xcb_window_t)attr_nonnull(1); void (*deinit)(backend_t *backend_data) attr_nonnull(1); /// Called when rendering will be stopped for an unknown amount of diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 4e3d713873..5e6f13cf9f 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -28,7 +28,8 @@ struct dummy_data { struct backend_image mask; }; -struct backend_base *dummy_init(struct session *ps attr_unused) { +struct backend_base * +dummy_init(struct session *ps attr_unused, xcb_window_t target attr_unused) { auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); ret->c = &ps->c; ret->loop = ps->loop; diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index a78c1903da..1156dd5922 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -130,7 +130,7 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { /** * Initialize OpenGL. */ -static backend_t *egl_init(session_t *ps) { +static backend_t *egl_init(session_t *ps, xcb_window_t target) { bool success = false; struct egl_data *gd = NULL; @@ -212,7 +212,7 @@ static backend_t *egl_init(session_t *ps) { // clang-format on gd->target_win = eglCreatePlatformWindowSurfaceProc( - gd->display, config, (xcb_window_t[]){session_get_target_window(ps)}, NULL); + gd->display, config, (xcb_window_t[]){target}, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); goto end; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 681bd17470..cb55d4d9b0 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -227,13 +227,13 @@ static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawab /** * Initialize OpenGL. */ -static backend_t *glx_init(session_t *ps) { +static backend_t *glx_init(session_t *ps, xcb_window_t target) { bool success = false; glxext_init(ps->c.dpy, ps->c.screen); auto gd = ccalloc(1, struct _glx_data); init_backend_base(&gd->gl.base, ps); - gd->target_win = session_get_target_window(ps); + gd->target_win = target; XVisualInfo *pvis = NULL; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index c359fc4efa..b175ceaf21 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -869,7 +869,7 @@ static void get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } -static backend_t *backend_xrender_init(session_t *ps) { +static backend_t *backend_xrender_init(session_t *ps, xcb_window_t target) { if (ps->o.dithered_present) { log_warn("\"dithered-present\" is not supported by the xrender backend."); } @@ -888,7 +888,7 @@ static backend_t *backend_xrender_init(session_t *ps) { xd->black_pixel = solid_picture(&ps->c, true, 1, 0, 0, 0); xd->white_pixel = solid_picture(&ps->c, true, 1, 1, 1, 1); - xd->target_win = session_get_target_window(ps); + xd->target_win = target; xcb_render_create_picture_value_list_t pa = { .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, }; diff --git a/src/diagnostic.c b/src/diagnostic.c index 1159d9eabc..ce93cfc0c2 100644 --- a/src/diagnostic.c +++ b/src/diagnostic.c @@ -39,7 +39,7 @@ void print_diagnostics(session_t *ps, const char *config_file, bool compositor_r for (int i = 0; i < NUM_BKEND; i++) { if (backend_list[i] && backend_list[i]->diagnostics) { printf("\n### Backend: %s\n\n", BACKEND_STRS[i]); - auto data = backend_list[i]->init(ps); + auto data = backend_list[i]->init(ps, session_get_target_window(ps)); if (!data) { printf(" Cannot initialize this backend\n"); } else { diff --git a/src/picom.c b/src/picom.c index bf18523f9f..c9772e3b17 100644 --- a/src/picom.c +++ b/src/picom.c @@ -605,7 +605,8 @@ static bool initialize_backend(session_t *ps) { assert(!ps->backend_data); // Reinitialize win_data assert(backend_list[ps->o.backend]); - ps->backend_data = backend_list[ps->o.backend]->init(ps); + ps->backend_data = + backend_list[ps->o.backend]->init(ps, session_get_target_window(ps)); if (!ps->backend_data) { log_fatal("Failed to initialize backend, aborting..."); quit(ps); From 825cc563e18a4823285615336a250484d56d9473 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 00:53:44 +0300 Subject: [PATCH 141/177] backend: egl: simplify usage of the target window parameter in egl_init --- src/backend/gl/egl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c index 1156dd5922..ca70359173 100644 --- a/src/backend/gl/egl.c +++ b/src/backend/gl/egl.c @@ -211,8 +211,8 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) { } // clang-format on - gd->target_win = eglCreatePlatformWindowSurfaceProc( - gd->display, config, (xcb_window_t[]){target}, NULL); + gd->target_win = + eglCreatePlatformWindowSurfaceProc(gd->display, config, &target, NULL); if (gd->target_win == EGL_NO_SURFACE) { log_error("Failed to create EGL surface."); goto end; From 156423c0104fbac7f3eb7c50c0fa4812345968fc Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 01:04:56 +0300 Subject: [PATCH 142/177] backend: dummy: change type of ps parameter to session_t in dummy_init --- src/backend/dummy/dummy.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 5e6f13cf9f..f4fa5f459f 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -28,8 +28,7 @@ struct dummy_data { struct backend_image mask; }; -struct backend_base * -dummy_init(struct session *ps attr_unused, xcb_window_t target attr_unused) { +struct backend_base *dummy_init(session_t *ps attr_unused, xcb_window_t target attr_unused) { auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); ret->c = &ps->c; ret->loop = ps->loop; From 8bf82857c4542877b4e2fa36761acb70f6fa2e2d Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 01:38:34 +0300 Subject: [PATCH 143/177] backend: name default backend operation implementations consistently --- src/backend/backend.h | 6 +++--- src/backend/backend_common.c | 4 ++-- src/backend/backend_common.h | 4 ++-- src/backend/dummy/dummy.c | 2 +- src/backend/xrender/xrender.c | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index 3ed7a761a8..6a05a55090 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -215,18 +215,18 @@ struct backend_operations { struct xvisual_info fmt, bool owned); /// Create a shadow context for rendering shadows with radius `radius`. - /// Default implementation: default_backend_create_shadow_context + /// Default implementation: default_create_shadow_context struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, double radius); /// Destroy a shadow context - /// Default implementation: default_backend_destroy_shadow_context + /// Default implementation: default_destroy_shadow_context void (*destroy_shadow_context)(backend_t *backend_data, struct backend_shadow_context *ctx); /// Create a shadow image based on the parameters. Resulting image should have a /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the /// shadow context is created. - /// Default implementation: default_backend_render_shadow + /// Default implementation: default_render_shadow /// /// Required. void *(*render_shadow)(backend_t *backend_data, int width, int height, diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 3388b78516..eef214593b 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -293,8 +293,8 @@ bool build_shadow(struct x_connection *c, double opacity, const int width, return false; } -void *default_backend_render_shadow(backend_t *backend_data, int width, int height, - struct backend_shadow_context *sctx, struct color color) { +void *default_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color) { const conv *kernel = (void *)sctx; xcb_render_picture_t shadow_pixel = solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue); diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 2e49cecf71..a58d816c0a 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -62,8 +62,8 @@ bool default_is_win_transparent(void *, win *, void *); /// caveat as `default_is_win_transparent` applies. bool default_is_frame_transparent(void *, win *, void *); -void *default_backend_render_shadow(backend_t *backend_data, int width, int height, - struct backend_shadow_context *sctx, struct color color); +void *default_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color); /// Implement `render_shadow` with `shadow_from_mask`. void * diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 4e3d713873..19e88ddc85 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -184,7 +184,7 @@ struct backend_operations dummy_ops = { .bind_pixmap = dummy_bind_pixmap, .create_shadow_context = default_create_shadow_context, .destroy_shadow_context = default_destroy_shadow_context, - .render_shadow = default_backend_render_shadow, + .render_shadow = default_render_shadow, .make_mask = dummy_make_mask, .release_image = dummy_release_image, .is_image_transparent = dummy_is_image_transparent, diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index c359fc4efa..5c7d786ef4 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -981,7 +981,7 @@ struct backend_operations xrender_ops = { .release_image = release_image, .create_shadow_context = default_create_shadow_context, .destroy_shadow_context = default_destroy_shadow_context, - .render_shadow = default_backend_render_shadow, + .render_shadow = default_render_shadow, .make_mask = make_mask, //.prepare_win = prepare_win, //.release_win = release_win, From bd3134efd982397a3c51a48a7d7f13e25b87c41d Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 01:42:00 +0300 Subject: [PATCH 144/177] backend: xrender: remove obsolete commented out backend operations --- src/backend/xrender/xrender.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 5c7d786ef4..f7ef8616c1 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -983,8 +983,6 @@ struct backend_operations xrender_ops = { .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_render_shadow, .make_mask = make_mask, - //.prepare_win = prepare_win, - //.release_win = release_win, .is_image_transparent = default_is_image_transparent, .buffer_age = buffer_age, .max_buffer_age = 2, From 44800ed845fad3fb0638891764a3b17084c96c3a Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Thu, 3 Aug 2023 01:44:54 +0300 Subject: [PATCH 145/177] backend: remove default implementations of obsolete backend operations --- src/backend/backend_common.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index a58d816c0a..da9ccd0235 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -54,14 +54,6 @@ solid_picture(struct x_connection *, bool argb, double a, double r, double g, do xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity, int width, int height); -/// The default implementation of `is_win_transparent`, it simply looks at win::mode. So -/// this is not suitable for backends that alter the content of windows -bool default_is_win_transparent(void *, win *, void *); - -/// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same -/// caveat as `default_is_win_transparent` applies. -bool default_is_frame_transparent(void *, win *, void *); - void *default_render_shadow(backend_t *backend_data, int width, int height, struct backend_shadow_context *sctx, struct color color); From e07b1d7a127950b4633fa5459341ab234e9e5bc6 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Sun, 10 Sep 2023 22:32:02 +0300 Subject: [PATCH 146/177] backend: gl: convert the shadow color to the premultiplied format to respect the globally set glBlendFunc and thus get the correct and expected result --- src/backend/gl/gl_common.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index d437fdd7bd..786e82f413 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -1347,8 +1347,11 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, glBindTexture(GL_TEXTURE_2D, tmp_texture); glUseProgram(gd->shadow_shader.prog); - glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red, - (GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha); + // The shadow color is converted to the premultiplied format to respect the + // globally set glBlendFunc and thus get the correct and expected result. + glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)(color.red * color.alpha), + (GLfloat)(color.green * color.alpha), + (GLfloat)(color.blue * color.alpha), (GLfloat)color.alpha); // clang-format off GLuint indices[] = {0, 1, 2, 2, 3, 0}; From a6b4e285f833c13b217c0e30ca71a903ec2464b5 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Tue, 17 Oct 2023 01:10:27 +0300 Subject: [PATCH 147/177] backend: xrender: don't leak the mask picture in the blur function --- src/backend/xrender/xrender.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index c359fc4efa..97268b1716 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -497,6 +497,9 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask to_i16_checked(extent_resized->y1), width_resized, height_resized); } + if (mask_allocated) { + x_free_picture(c, mask_pict); + } x_free_picture(c, tmp_picture[0]); x_free_picture(c, tmp_picture[1]); pixman_region32_fini(®_op); From 47bb825b2c4965d1874d983c7128af0fbfb6b4cb Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Dec 2023 08:39:02 +0000 Subject: [PATCH 148/177] backend: glx: don't return false from a function returning void * Signed-off-by: Yuxuan Shui --- src/backend/gl/glx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index cb55d4d9b0..78143a2b77 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -371,12 +371,12 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b if (fmt.visual_depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than max possible depth %d.", fmt.visual_depth, OPENGL_MAX_DEPTH); - return false; + return NULL; } if (fmt.visual_depth < 0) { log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth); - return false; + return NULL; } auto r = From 1b97f18e5f037d40a59f2dab9a32037b7a735f96 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Dec 2023 08:50:47 +0000 Subject: [PATCH 149/177] compiler: bring the unreachable macro in line with C23 Signed-off-by: Yuxuan Shui --- src/c2.c | 2 +- src/compiler.h | 10 ++++++---- src/utils.c | 2 +- src/win.c | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/c2.c b/src/c2.c index d82448aa24..7f1f80e0f7 100644 --- a/src/c2.c +++ b/src/c2.c @@ -1336,7 +1336,7 @@ static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE; default: assert(0); break; } - unreachable; + unreachable(); } /** diff --git a/src/compiler.h b/src/compiler.h index 76326707b0..af8fb83190 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -101,10 +101,12 @@ # endif #endif -#if defined(__GNUC__) || defined(__clang__) -# define unreachable __builtin_unreachable() -#else -# define unreachable do {} while(0) +#ifndef unreachable +# if defined(__GNUC__) || defined(__clang__) +# define unreachable() __builtin_unreachable() +# else +# define unreachable() do {} while(0) +# endif #endif #ifndef __has_include diff --git a/src/utils.c b/src/utils.c index d26afa331e..5709fa21d2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -30,7 +30,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v)); abort(); - unreachable; + unreachable(); } /// diff --git a/src/win.c b/src/win.c index bd19b3d412..6dccbed652 100644 --- a/src/win.c +++ b/src/win.c @@ -2433,7 +2433,7 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) { case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true; case WSTATE_MAPPING: map_win_finish(w); return false; case WSTATE_FADING: w->state = WSTATE_MAPPED; break; - default: unreachable; + default: unreachable(); } } From e92745671baf07cce8044c99451418f1aa29f25a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Dec 2023 08:55:23 +0000 Subject: [PATCH 150/177] compiler: use C23 auto when available One annoying thing is C23 still defines auto as a storage class despite it now being used for type inference. As a consequence we must write "auto const" instead of the more natural "const auto". Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 20 ++++++++++---------- src/compiler.h | 2 ++ src/picom.c | 4 ++-- src/render.c | 20 ++++++++++---------- src/win.c | 6 +++--- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index cc6e5f430a..5d04ff272b 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -176,8 +176,8 @@ static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrende *allocated = false; return inner->pict; } - const auto tmpw = to_u16_checked(inner->width); - const auto tmph = to_u16_checked(inner->height); + auto const tmpw = to_u16_checked(inner->width); + auto const tmph = to_u16_checked(inner->height); *allocated = true; x_clear_picture_clip_region(xd->base.c, inner->pict); auto ret = x_create_picture_with_visual( @@ -226,13 +226,13 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, region_t reg; bool has_alpha = inner->has_alpha || img->opacity != 1; - const auto tmpw = to_u16_checked(inner->width); - const auto tmph = to_u16_checked(inner->height); - const auto tmpew = to_u16_checked(img->ewidth); - const auto tmpeh = to_u16_checked(img->eheight); + auto const tmpw = to_u16_checked(inner->width); + auto const tmph = to_u16_checked(inner->height); + auto const tmpew = to_u16_checked(img->ewidth); + auto const tmpeh = to_u16_checked(img->eheight); // Remember: the mask has a 1-pixel border - const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1); - const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1); + auto const mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1); + auto const mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1); const xcb_render_color_t dim_color = { .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)}; @@ -395,8 +395,8 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask resize_region(®_op, bctx->resize_width, bctx->resize_height); const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized); - const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1); - const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1); + auto const height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1); + auto const width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1); static const char *filter0 = "Nearest"; // The "null" filter static const char *filter = "convolution"; diff --git a/src/compiler.h b/src/compiler.h index af8fb83190..302bc7bbed 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -7,7 +7,9 @@ #endif // clang-format off +#if __STDC_VERSION__ <= 201710L #define auto __auto_type +#endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define likely_if(x) if (likely(x)) diff --git a/src/picom.c b/src/picom.c index c9772e3b17..1302f51465 100644 --- a/src/picom.c +++ b/src/picom.c @@ -265,7 +265,7 @@ void schedule_render(session_t *ps, bool triggered_by_vblank) { goto schedule; } - const auto deadline = ps->last_msc_instant + (unsigned long)divisor * frame_time; + auto const deadline = ps->last_msc_instant + (unsigned long)divisor * frame_time; unsigned int available = 0; if (deadline > now_us) { available = (unsigned int)(deadline - now_us); @@ -1158,7 +1158,7 @@ static int register_cm(session_t *ps) { // Set WM_CLIENT_MACHINE. As per EWMH, because we set _NET_WM_PID, we must also // set WM_CLIENT_MACHINE. { - const auto hostname_max = (unsigned long)sysconf(_SC_HOST_NAME_MAX); + auto const hostname_max = (unsigned long)sysconf(_SC_HOST_NAME_MAX); char *hostname = malloc(hostname_max); if (gethostname(hostname, hostname_max) == 0) { diff --git a/src/render.c b/src/render.c index 3a7e844842..ec50d8a3ec 100644 --- a/src/render.c +++ b/src/render.c @@ -474,10 +474,10 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) } else { // Painting parameters const margin_t extents = win_calc_frame_extents(w); - const auto t = extents.top; - const auto l = extents.left; - const auto b = extents.bottom; - const auto r = extents.right; + auto const t = extents.top; + auto const l = extents.left; + auto const b = extents.bottom; + auto const r = extents.right; #define COMP_BDR(cx, cy, cwid, chei) \ paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * w->opacity, \ @@ -888,8 +888,8 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t const region_t *reg_paint) { const int16_t x = w->g.x; const int16_t y = w->g.y; - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); + auto const wid = to_u16_checked(w->widthb); + auto const hei = to_u16_checked(w->heightb); const int cr = w ? w->corner_radius : 0; double factor_center = 1.0; @@ -1174,8 +1174,8 @@ void paint_all(session_t *ps, struct managed_win *t) { if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) { const int16_t x = w->g.x; const int16_t y = w->g.y; - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); + auto const wid = to_u16_checked(w->widthb); + auto const hei = to_u16_checked(w->heightb); glx_bind_texture(ps, &w->glx_texture_bg, x, y, wid, hei); } #endif @@ -1195,8 +1195,8 @@ void paint_all(session_t *ps, struct managed_win *t) { // Rounded corners for XRender is implemented inside render() // Round window corners if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) { - const auto wid = to_u16_checked(w->widthb); - const auto hei = to_u16_checked(w->heightb); + auto const wid = to_u16_checked(w->widthb); + auto const hei = to_u16_checked(w->heightb); glx_round_corners_dst(ps, w, w->glx_texture_bg, w->g.x, w->g.y, wid, hei, (float)ps->psglx->z - 0.5F, diff --git a/src/win.c b/src/win.c index 6dccbed652..901b646cae 100644 --- a/src/win.c +++ b/src/win.c @@ -2758,7 +2758,7 @@ void win_clear_flags(struct managed_win *w, uint64_t flags) { } void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) { - const auto bits_per_element = sizeof(*w->stale_props) * 8; + auto const bits_per_element = sizeof(*w->stale_props) * 8; size_t new_capacity = w->stale_props_capacity; // Calculate the new capacity of the properties array @@ -2793,12 +2793,12 @@ static void win_clear_all_properties_stale(struct managed_win *w) { } static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) { - const auto bits_per_element = sizeof(*w->stale_props) * 8; + auto const bits_per_element = sizeof(*w->stale_props) * 8; if (prop >= w->stale_props_capacity * bits_per_element) { return false; } - const auto mask = 1UL << (prop % bits_per_element); + auto const mask = 1UL << (prop % bits_per_element); bool ret = w->stale_props[prop / bits_per_element] & mask; w->stale_props[prop / bits_per_element] &= ~mask; return ret; From 268e1d890ab7d982cbcbec034c3ccebd09c8b10b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Dec 2023 23:34:34 +0000 Subject: [PATCH 151/177] core: exit if paint_preprocess fails This usually means there is another compositor running. If we don't do this picom will spin forever. Fixes #1104 Signed-off-by: Yuxuan Shui --- src/picom.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/picom.c b/src/picom.c index 1302f51465..fa9182bf12 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1741,6 +1741,11 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { auto bottom = paint_preprocess(ps, &fade_running, &animation); ps->tmout_unredir_hit = false; + if (!bottom) { + log_fatal("Pre-render preparation has failed, exiting..."); + exit(1); + } + if (!was_redirected && ps->redirected) { // paint_preprocess redirected the screen, which might change the state of // some of the windows (e.g. the window image might become stale). From 7366553be2b825495c5b1e09be09d0fabde4b9b4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 17 Dec 2023 23:44:56 +0000 Subject: [PATCH 152/177] core: distinguish preprocess failure and no window to render paint_process would return NULL for both of these cases, but we should exit in the failure case, and continue going in the other. Signed-off-by: Yuxuan Shui --- src/picom.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/picom.c b/src/picom.c index fa9182bf12..6f3c2908d9 100644 --- a/src/picom.c +++ b/src/picom.c @@ -769,13 +769,19 @@ static void handle_root_flags(session_t *ps) { } } -static struct managed_win * -paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { +/** + * Go through the window stack and calculate some parameters for rendering. + * + * @return whether the operation succeeded + */ +static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation, + struct managed_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 managed_win *bottom = NULL; *fade_running = false; *animation = false; + *out_bottom = NULL; // Fading step calculation long long steps = 0L; @@ -1036,12 +1042,13 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { ev_timer_stop(ps->loop, &ps->unredir_timer); if (!ps->redirected) { if (!redirect_start(ps)) { - return NULL; + return false; } } } - return bottom; + *out_bottom = bottom; + return true; } void root_damaged(session_t *ps) { @@ -1738,14 +1745,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { bool fade_running = false; bool animation = false; bool was_redirected = ps->redirected; - auto bottom = paint_preprocess(ps, &fade_running, &animation); - ps->tmout_unredir_hit = false; - - if (!bottom) { + struct managed_win *bottom = NULL; + if (!paint_preprocess(ps, &fade_running, &animation, &bottom)) { log_fatal("Pre-render preparation has failed, exiting..."); exit(1); } + ps->tmout_unredir_hit = false; + if (!was_redirected && ps->redirected) { // paint_preprocess redirected the screen, which might change the state of // some of the windows (e.g. the window image might become stale). From aed2a205ed30a5c65ad86f98a3bfae4b1ae3bff8 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 04:11:18 +0000 Subject: [PATCH 153/177] log: add a new log level, verbose Signed-off-by: Yuxuan Shui --- src/log.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/log.h b/src/log.h index e40fe3c8f4..6d74126c06 100644 --- a/src/log.h +++ b/src/log.h @@ -9,11 +9,19 @@ enum log_level { LOG_LEVEL_INVALID = -1, + /// Very noisy debug messages, many lines per frame. LOG_LEVEL_TRACE = 0, + /// Frequent debug messages, a few lines per frame. + LOG_LEVEL_VERBOSE, + /// Less frequent debug messages. LOG_LEVEL_DEBUG, + /// Informational messages. LOG_LEVEL_INFO, + /// Warnings. LOG_LEVEL_WARN, + /// Errors. LOG_LEVEL_ERROR, + /// Fatal errors. LOG_LEVEL_FATAL, }; @@ -31,6 +39,7 @@ enum log_level { } \ } while (0) #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__) +#define log_verbose(x, ...) LOG_UNLIKELY(VERBOSE, x, ##__VA_ARGS__) #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__) #define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__) #define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__) From c7ec4c2c1624cdfd2ad8936e3cdf70023f62459b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 04:15:27 +0000 Subject: [PATCH 154/177] log: fix printing and parsing of the verbose log level Signed-off-by: Yuxuan Shui --- src/log.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/log.c b/src/log.c index 870d755750..99f310a654 100644 --- a/src/log.c +++ b/src/log.c @@ -68,6 +68,7 @@ log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { static attr_const const char *log_level_to_string(enum log_level level) { switch (level) { case LOG_LEVEL_TRACE: return "TRACE"; + case LOG_LEVEL_VERBOSE: return "VERBOSE"; case LOG_LEVEL_DEBUG: return "DEBUG"; case LOG_LEVEL_INFO: return "INFO"; case LOG_LEVEL_WARN: return "WARN"; @@ -81,6 +82,9 @@ enum log_level string_to_log_level(const char *str) { if (strcasecmp(str, "TRACE") == 0) { return LOG_LEVEL_TRACE; } + if (strcasecmp(str, "VERBOSE") == 0) { + return LOG_LEVEL_VERBOSE; + } if (strcasecmp(str, "DEBUG") == 0) { return LOG_LEVEL_DEBUG; } @@ -234,12 +238,10 @@ struct log_target *null_logger_new(void) { static void null_logger_write(struct log_target *tgt attr_unused, const char *str attr_unused, size_t len attr_unused) { - return; } static void null_logger_writev(struct log_target *tgt attr_unused, const struct iovec *vec attr_unused, int vcnt attr_unused) { - return; } static const struct log_ops null_logger_ops = { From e60cb65672e585ad250a123794185350c0f41f18 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 04:18:18 +0000 Subject: [PATCH 155/177] log: give verbose log level a color Signed-off-by: Yuxuan Shui --- src/log.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/log.c b/src/log.c index 99f310a654..664b9f41be 100644 --- a/src/log.c +++ b/src/log.c @@ -277,6 +277,7 @@ static void file_logger_destroy(struct log_target *tgt) { static const char *terminal_colorize_begin(enum log_level level) { switch (level) { case LOG_LEVEL_TRACE: return ANSI("30;2"); + case LOG_LEVEL_VERBOSE: case LOG_LEVEL_DEBUG: return ANSI("37;2"); case LOG_LEVEL_INFO: return ANSI("92"); case LOG_LEVEL_WARN: return ANSI("33"); From fd9c52d0ee97154a84433b48a4a4f2beecb25053 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Thu, 19 Jan 2023 23:52:58 +0100 Subject: [PATCH 156/177] backend: xrender: force 32bit ARGB visual when adding rounded corners Force the temporary picture to a 32-bit ARGB visual when masking it for rounded corners to keep the transparency information. Otherwise, windows with a 24-bit visual would get black corners instead. Authored-by: Bernd Busse Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 5d04ff272b..b2ec3f2301 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -250,10 +250,16 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, } if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) { // Apply image properties using a temporary image, because the source - // image is transparent. Otherwise the properties can be applied directly - // on the target image. + // image is transparent or will get transparent corners. Otherwise the + // properties can be applied directly on the target image. + // Also force a 32-bit ARGB visual for transparent corners, otherwise the + // corners become black. + auto visual = + (img->corner_radius != 0 && inner->depth != 32) + ? x_get_visual_for_standard(xd->base.c, XCB_PICT_STANDARD_ARGB_32) + : inner->visual; auto tmp_pict = x_create_picture_with_visual( - xd->base.c, inner->width, inner->height, inner->visual, 0, NULL); + xd->base.c, inner->width, inner->height, visual, 0, NULL); // Set clip region translated to source coordinate x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x), From e76cf43f021d8c02c4dd6e5cd91475bb27b51dad Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 11:30:09 +0000 Subject: [PATCH 157/177] backend: gl: adjust some log levels Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index f1900af913..328756fc30 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -604,9 +604,9 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, bind_uniform(pass, mask_offset); bind_uniform(pass, mask_inverted); bind_uniform(pass, mask_corner_radius); - log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex, - pass->uniform_mask_offset, pass->uniform_mask_inverted, - pass->uniform_mask_corner_radius, pass->uniform_opacity); + log_debug("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex, + pass->uniform_mask_offset, pass->uniform_mask_inverted, + pass->uniform_mask_corner_radius, pass->uniform_opacity); pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); // Setup projection matrix From 8d98b7d6392aa2327f1c96b725c5774cfeefba53 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 18 Jun 2023 17:12:57 +0100 Subject: [PATCH 158/177] core: don't call schedule_render too early I mistakenly assumed that PresentCompleteNotify event signifies the end of a vblank (or the start of scanout). But actually this event can in theory in sent at any point during a vblank, with its timestamp pointing to when the end of vblank is. (that's why we often find the timestamp to be in the future). Add a delay so schedule_render is actually called at the end of vblank, so it doesn't mistakenly think the render is too slow to complete. Signed-off-by: Yuxuan Shui --- src/common.h | 2 ++ src/event.c | 3 ++- src/picom.c | 37 +++++++++++++++++++++++++++---------- src/utils.h | 22 ++++++++++++++++------ 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/common.h b/src/common.h index 707d00e714..f9e1765ebd 100644 --- a/src/common.h +++ b/src/common.h @@ -147,6 +147,8 @@ typedef struct session { ev_timer fade_timer; /// Use an ev_timer callback for drawing ev_timer draw_timer; + /// Timer for the end of each vblanks. Used for calling schedule_render. + ev_timer vblank_timer; /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue diff --git a/src/event.c b/src/event.c index 807cf4ed96..739540dc9a 100644 --- a/src/event.c +++ b/src/event.c @@ -716,7 +716,8 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { // XXX redraw needs to be more fine grained queue_redraw(ps); - // the events sent from SendEvent will be ignored + // We intentionally ignore events sent via SendEvent. Those events has the 8th bit + // of response_type set, meaning they will match none of the cases below. switch (ev->response_type) { case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break; case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break; diff --git a/src/picom.c b/src/picom.c index 6f3c2908d9..8238ae8f09 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1515,13 +1515,8 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - uint64_t now_usec = (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000); - uint64_t drift; - if (cne->ust > now_usec) { - drift = cne->ust - now_usec; - } else { - drift = now_usec - cne->ust; - } + auto now_us = (int64_t)(now.tv_sec * 1000000L + now.tv_nsec / 1000); + auto drift = iabs((int64_t)cne->ust - now_us); if (ps->last_msc_instant != 0) { auto frame_count = cne->msc - ps->last_msc; @@ -1537,14 +1532,28 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ // align with the monotonic clock. If not, disable frame pacing because we // can't schedule frames reliably. log_error("Temporal anomaly detected, frame pacing disabled. (Are we " - "running inside a time namespace?), %" PRIu64 " %" PRIu64, - now_usec, ps->last_msc_instant); + "running inside a time namespace?), %" PRIi64 " %" PRIu64, + now_us, ps->last_msc_instant); ps->frame_pacing = false; } ps->last_msc_instant = cne->ust; ps->last_msc = cne->msc; if (ps->redraw_needed) { - schedule_render(ps, true); + if (now_us > (int64_t)cne->ust) { + schedule_render(ps, true); + } else { + // Wait until the end of the current vblank to call + // schedule_render. If we call schedule_render too early, it can + // mistakenly think the render missed the vblank, and doesn't + // schedule render for the next vblank, causing frame drops. + log_trace("The end of this vblank is %" PRIi64 " us into the " + "future", + (int64_t)cne->ust - now_us); + assert(!ev_is_active(&ps->vblank_timer)); + ev_timer_set(&ps->vblank_timer, + ((double)cne->ust - (double)now_us) / 1000000.0, 0); + ev_timer_start(ps->loop, &ps->vblank_timer); + } } } @@ -1823,6 +1832,13 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) { } } +static void schedule_render_callback(EV_P_ ev_timer *w, int revents attr_unused) { + session_t *ps = session_ptr(w, vblank_timer); + ev_timer_stop(EV_A_ w); + + schedule_render(ps, true); +} + static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { session_t *ps = (session_t *)w; xcb_generic_event_t *ev = xcb_poll_for_event(ps->c.c); @@ -2419,6 +2435,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); ev_init(&ps->draw_timer, draw_callback); + ev_init(&ps->vblank_timer, schedule_render_callback); ev_init(&ps->fade_timer, fade_timer_callback); diff --git a/src/utils.h b/src/utils.h index fc6dd306de..446fba8be7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -125,14 +125,22 @@ safe_isnan(double a) { * @param max maximum value * @return normalized value */ -static inline int attr_const normalize_i_range(int i, int min, int max) { - if (i > max) +static inline int attr_const attr_unused normalize_i_range(int i, int min, int max) { + if (i > max) { return max; - if (i < min) + } + if (i < min) { return min; + } return i; } +/// Generic integer abs() +#define iabs(val) \ + ({ \ + __auto_type __tmp = (val); \ + __tmp > 0 ? __tmp : -__tmp; \ + }) #define min2(a, b) ((a) > (b) ? (b) : (a)) #define max2(a, b) ((a) > (b) ? (a) : (b)) #define min3(a, b, c) min2(a, min2(b, c)) @@ -149,10 +157,12 @@ static inline int attr_const normalize_i_range(int i, int min, int max) { * @return normalized value */ static inline double attr_const normalize_d_range(double d, double min, double max) { - if (d > max) + if (d > max) { return max; - if (d < min) + } + if (d < min) { return min; + } return d; } @@ -162,7 +172,7 @@ static inline double attr_const normalize_d_range(double d, double min, double m * @param d double value to normalize * @return normalized value */ -static inline double attr_const normalize_d(double d) { +static inline double attr_const attr_unused normalize_d(double d) { return normalize_d_range(d, 0.0, 1.0); } From 827380e1e348893ef568b647bd49ee9f7d61e577 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 23 Jun 2023 00:17:35 +0100 Subject: [PATCH 159/177] core: simplify the pacing logic a little bit Make it simpler to stop requesting PresentCompleteNotify when there is nothing to render. Related: #1079 Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 14 ++- src/backend/backend.h | 6 +- src/common.h | 25 +++-- src/picom.c | 232 +++++++++++++++++++++++------------------- 4 files changed, 162 insertions(+), 115 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index 33a3d8bcea..dd3366d66a 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -82,13 +82,17 @@ void handle_device_reset(session_t *ps) { } /// paint all windows -void paint_all_new(session_t *ps, struct managed_win *t) { +/// +/// Returns if any render command is issued. IOW if nothing on the screen has changed, +/// this function will return false. +bool paint_all_new(session_t *ps, struct managed_win *const t) { struct timespec now = get_time_timespec(); auto paint_all_start_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000; if (ps->backend_data->ops->device_status && ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) { - return handle_device_reset(ps); + handle_device_reset(ps); + return false; } if (ps->o.xrender_sync_fence) { if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) { @@ -114,7 +118,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { if (!pixman_region32_not_empty(®_damage)) { pixman_region32_fini(®_damage); - return; + return false; } #ifdef DEBUG_REPAINT @@ -199,7 +203,6 @@ void paint_all_new(session_t *ps, struct managed_win *t) { ps->last_schedule_delay = after_damage_us - ps->next_render; } } - ps->did_render = true; if (ps->backend_data->ops->prepare) { ps->backend_data->ops->prepare(ps->backend_data, ®_paint); @@ -219,7 +222,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { // 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) { + for (struct managed_win *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)); @@ -541,6 +544,7 @@ void paint_all_new(session_t *ps, struct managed_win *t) { for (win *w = t; w; w = w->prev_trans) log_trace(" %#010lx", w->id); #endif + return true; } // vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend.h b/src/backend/backend.h index f572a93583..1b4df729d0 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -366,4 +366,8 @@ struct backend_operations { extern struct backend_operations *backend_list[]; -void paint_all_new(session_t *ps, struct managed_win *const t) attr_nonnull(1); +/// paint all windows +/// +/// Returns if any render command is issued. IOW if nothing on the screen has changed, +/// this function will return false. +bool paint_all_new(session_t *ps, struct managed_win *t) attr_nonnull(1); diff --git a/src/common.h b/src/common.h index f9e1765ebd..ac9da4c733 100644 --- a/src/common.h +++ b/src/common.h @@ -134,6 +134,17 @@ struct shader_info { UT_hash_handle hh; }; +enum render_progress { + /// Render is finished and presented to the screen. + RENDER_IDLE = 0, + /// Rendering is queued, but not started yet. + RENDER_QUEUED, + /// Backend has been called, render commands have been issued. + RENDER_STARTED, + /// Backend reported render commands have been finished. (not actually used). + RENDER_FINISHED, +}; + /// Structure containing all necessary data for a session. typedef struct session { // === Event handlers === @@ -223,17 +234,11 @@ typedef struct session { uint64_t last_msc_instant; /// The last MSC number uint64_t last_msc; - /// When the currently rendered frame will be displayed. - /// 0 means there is no pending frame. - uint64_t target_msc; /// The delay between when the last frame was scheduled to be rendered, and when /// the render actually started. uint64_t last_schedule_delay; /// When do we want our next frame to start rendering. uint64_t next_render; - /// Did we actually render the last frame. Sometimes redraw will be scheduled only - /// to find out nothing has changed. In which case this will be set to false. - bool did_render; /// Whether we can perform frame pacing. bool frame_pacing; @@ -247,7 +252,13 @@ typedef struct session { options_t o; /// Whether we have hit unredirection timeout. bool tmout_unredir_hit; - /// Whether we need to redraw the screen + /// Rendering is currently in progress. This means we are in any stage of + /// rendering a frame. The render could be queued but not yet started, or it could + /// have finished but not yet presented. + enum render_progress render_in_progress; + /// Whether there are changes pending for the next render. A render is currently + /// in progress, otherwise we would have started a new render instead of setting + /// this flag. bool redraw_needed; /// Cache a xfixes region so we don't need to allocate it every time. diff --git a/src/picom.c b/src/picom.c index 8238ae8f09..75d914934a 100644 --- a/src/picom.c +++ b/src/picom.c @@ -167,19 +167,31 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t /// /// Renders are scheduled like this: /// -/// 1. queue_redraw() registers the intention to render. redraw_needed is set to true to -/// indicate what is on screen needs to be updated. +/// 1. queue_redraw() queues a new render by calling schedule_render, if there is no +/// render currently scheduled. i.e. render_in_progress == RENDER_IDLE. /// 2. then, we need to figure out the best time to start rendering. first, we need to -/// know when the next frame will be displayed on screen. we have this information from -/// the Present extension: we know when was the last frame displayed, and we know the -/// refresh rate. so we can calculate the next frame's display time. if our render time -/// estimation shows we could miss that target, we push the target back one frame. -/// 3. if there is already render completed for that target frame, or there is a render -/// currently underway, we don't do anything, and wait for the next Present Complete -/// Notify event to try to schedule again. -/// 4. otherwise, we schedule a render for that target frame. we use past statistics about -/// how long our renders took to figure out when to start rendering. we start rendering -/// at the latest point of time possible to still hit the target frame. +/// know when the current vblank will end. we have this information from the Present +/// extension: we know when was the end of last vblank, and we know the refresh rate. +/// so we can calculate the end of the current vblank. if our render time estimation +/// shows we could miss that target, we push the target back an integer number of +/// frames. and we calculate the end of the target vblank similarly. +/// 3. We schedule a render for that target. we use past statistics about how long our +/// renders took to figure out when to start rendering. we start rendering as late as +/// possible, but not too late that we miss the target vblank. render_in_progress is +/// set to RENDER_QUEUED. +/// 4. draw_callback() is called at the schedule time. Backend APIs are called to issue +/// render commands. render_in_progress is set to RENDER_STARTED. +/// 5. PresentCompleteNotify is received, which gives us the actual time when the current +/// vblank will end/ended. We schedule a call to handle_end_of_vblank at the +/// appropriate time. +/// 6. in handle_end_of_vblank, we check the backend to see if the render has finished. if +/// not, render_in_progress is unchanged; otherwise, render_in_progress is set to +/// RENDER_IDLE, and the next frame can be scheduled. +/// +/// This 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 @@ -188,80 +200,34 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t /// when the schedule is triggered by a steady timer, schedule_render will be called at a /// predictable offset into each vblank. -void schedule_render(session_t *ps, bool triggered_by_vblank) { +void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { + // 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; - ps->next_render = 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) { - // Not doing frame pacing, schedule a render immediately, if not already - // scheduled. - // If not redirected, we schedule immediately to have a chance to - // redirect. We won't have frame or render timing information anyway. + // If not doing frame pacing, schedule a render immediately unless it's + // already scheduled; if not redirected, we schedule immediately to have a + // chance to redirect. We won't have frame or render timing information + // anyway. if (!ev_is_active(&ps->draw_timer)) { - // We don't know the msc, so we set it to 1, because 0 is a - // special value - ps->target_msc = 1; goto schedule; } return; } - struct timespec render_time; - bool completed = - ps->backend_data->ops->last_render_time(ps->backend_data, &render_time); - if (!completed || ev_is_active(&ps->draw_timer)) { - // There is already a render underway (either just scheduled, or is - // rendered but awaiting completion), don't schedule another one. - if (ps->target_msc <= ps->last_msc) { - log_debug("Target frame %ld is in the past, but we are still " - "rendering", - ps->target_msc); - // We missed our target, push it back one frame - ps->target_msc = ps->last_msc + 1; - } - log_trace("Still rendering for target frame %ld, not scheduling another " - "render", - ps->target_msc); - return; - } - if (ps->target_msc > ps->last_msc) { - // Render for the target frame is completed, but is yet to be displayed. - // Don't schedule another render. - log_trace("Target frame %ld is in the future, and we have already " - "rendered, last msc: %d", - ps->target_msc, (int)ps->last_msc); - return; - } - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; - if (triggered_by_vblank) { - log_trace("vblank schedule delay: %ld us", now_us - ps->last_msc_instant); - } - - int render_time_us = - (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); - if (ps->target_msc == ps->last_msc) { - // The frame has just been displayed, record its render time; - if (ps->did_render) { - log_trace("Last render call took: %d (gpu) + %d (cpu) us, " - "last_msc: %" PRIu64, - render_time_us, (int)ps->last_schedule_delay, ps->last_msc); - render_statistics_add_render_time_sample( - &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); - } - ps->target_msc = 0; - ps->did_render = false; - ps->last_schedule_delay = 0; - } - unsigned int divisor = 0; auto render_budget = render_statistics_get_budget(&ps->render_stats, &divisor); 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. - ps->target_msc = ps->last_msc + 1; goto schedule; } @@ -271,14 +237,11 @@ void schedule_render(session_t *ps, bool triggered_by_vblank) { available = (unsigned int)(deadline - now_us); } - ps->target_msc = ps->last_msc + divisor; if (available > render_budget) { delay_s = (double)(available - render_budget) / 1000000.0; ps->next_render = deadline - render_budget; - } else { - delay_s = 0; - ps->next_render = now_us; } + 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" @@ -288,16 +251,57 @@ void schedule_render(session_t *ps, bool triggered_by_vblank) { log_trace("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, frame_time: " "%" PRIu32 ", now_us: %" PRIu64 ", next_msc: %" PRIu64 ", " - "target_msc: %" PRIu64 ", divisor: %d", + "divisor: %d", delay_s, ps->last_msc_instant, render_budget, frame_time, now_us, - deadline, ps->target_msc, divisor); + deadline, divisor); schedule: + ps->render_in_progress = RENDER_QUEUED; + ps->redraw_needed = false; assert(!ev_is_active(&ps->draw_timer)); ev_timer_set(&ps->draw_timer, delay_s, 0); ev_timer_start(ps->loop, &ps->draw_timer); } +/// Called after a vblank has ended +/// +/// Check if previously queued render has finished, and record the time it took. +void handle_end_of_vblank(session_t *ps) { + if (ps->render_in_progress != RENDER_STARTED) { + // We didn't start rendering for this vblank, nothing to do + return; + } + + // We shouldn't have scheduled a render if the previous render hasn't been + // presented yet. + assert(!ev_is_active(&ps->draw_timer)); + + 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, keep render_in_progress as RENDER_STARTED + log_debug("Last render did not complete during vblank, msc: %" PRIu64, + ps->last_msc); + return; + } + + int render_time_us = + (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); + // The frame has been finished and presented, record its render time. + log_trace("Last render call took: %d (gpu) + %d (cpu) us, " + "last_msc: %" PRIu64, + render_time_us, (int)ps->last_schedule_delay, ps->last_msc); + render_statistics_add_render_time_sample( + &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); + ps->last_schedule_delay = 0; + ps->render_in_progress = RENDER_IDLE; + + if (ps->redraw_needed) { + schedule_render(ps, true); + } +} + void queue_redraw(session_t *ps) { if (ps->screen_is_off) { // The screen is off, if there is a draw queued for the next frame (i.e. @@ -314,10 +318,14 @@ void queue_redraw(session_t *ps) { // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. // If --benchmark is used, redraw is always queued - if (!ps->redraw_needed && !ps->o.benchmark) { + if (ps->render_in_progress == RENDER_IDLE && !ps->o.benchmark) { schedule_render(ps, false); + } else if (ps->render_in_progress > RENDER_QUEUED) { + // render_in_progress > RENDER_QUEUED means we have already issued the + // render commands, so a new render must be scheduled to reflect new + // changes. Otherwise the queued render will include1 the new changes. + ps->redraw_needed = true; } - ps->redraw_needed = true; } /** @@ -1423,7 +1431,6 @@ static bool redirect_start(session_t *ps) { ps->last_msc_instant = 0; ps->last_msc = 0; ps->last_schedule_delay = 0; - ps->target_msc = 0; render_statistics_reset(&ps->render_stats); } else if (ps->frame_pacing) { log_error("Present extension is not supported, frame pacing disabled."); @@ -1487,6 +1494,10 @@ static void unredirect(session_t *ps) { log_debug("Screen unredirected."); } +/// Handle PresentCompleteNotify events +/// +/// Record the MSC value and their timestamps, and schedule handle_end_of_vblank() at the +/// correct time. static void handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_t *cne) { if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) { @@ -1538,22 +1549,24 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ } ps->last_msc_instant = cne->ust; ps->last_msc = cne->msc; - if (ps->redraw_needed) { - if (now_us > (int64_t)cne->ust) { - schedule_render(ps, true); - } else { - // Wait until the end of the current vblank to call - // schedule_render. If we call schedule_render too early, it can - // mistakenly think the render missed the vblank, and doesn't - // schedule render for the next vblank, causing frame drops. - log_trace("The end of this vblank is %" PRIi64 " us into the " - "future", - (int64_t)cne->ust - now_us); - assert(!ev_is_active(&ps->vblank_timer)); - ev_timer_set(&ps->vblank_timer, - ((double)cne->ust - (double)now_us) / 1000000.0, 0); - ev_timer_start(ps->loop, &ps->vblank_timer); - } + // Note we can't update ps->render_in_progress here because of this present + // complete notify, as we don't know if the render finished before the end of + // vblank or not. We schedule a call to handle_end_of_vblank() to figure out if we + // are still rendering, and update ps->render_in_progress accordingly. + if (now_us > (int64_t)cne->ust) { + handle_end_of_vblank(ps); + } else { + // Wait until the end of the current vblank to call + // handle_end_of_vblank. If we call it too early, it can + // mistakenly think the render missed the vblank, and doesn't + // schedule render for the next vblank, causing frame drops. + log_trace("The end of this vblank is %" PRIi64 " us into the " + "future", + (int64_t)cne->ust - now_us); + assert(!ev_is_active(&ps->vblank_timer)); + ev_timer_set(&ps->vblank_timer, + ((double)cne->ust - (double)now_us) / 1000000.0, 0); + ev_timer_start(ps->loop, &ps->vblank_timer); } } @@ -1788,13 +1801,14 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { log_trace("paint_preprocess took: %" PRIi64 " us", after_preprocess_us - after_handle_pending_updates_us); - // If the screen is unredirected, free all_damage to stop painting + // If the screen is unredirected, we don't render anything. + bool did_render = false; if (ps->redirected && ps->o.stoppaint_force != ON) { static int paint = 0; log_trace("Render start, frame %d", paint); if (!ps->o.legacy_backends) { - paint_all_new(ps, bottom); + did_render = paint_all_new(ps, bottom); } else { paint_all(ps, bottom); } @@ -1807,6 +1821,20 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { } } + if (ps->frame_pacing && did_render) { + ps->render_in_progress = RENDER_STARTED; + } else { + // With frame pacing, we set render_in_progress to RENDER_IDLE after the + // end of vblank. Without frame pacing, we won't be receiving vblank + // events, so we set render_in_progress to RENDER_IDLE here, right after + // we issue the render commands. + // The other case is if we decided there is no change to render, in that + // case no render command is issued, so we also set render_in_progress to + // RENDER_IDLE. + ps->render_in_progress = RENDER_IDLE; + } + ps->next_render = 0; + if (!fade_running) { ps->fade_time = 0L; } @@ -1832,11 +1860,11 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) { } } -static void schedule_render_callback(EV_P_ ev_timer *w, int revents attr_unused) { +static void vblank_callback(EV_P_ ev_timer *w, int revents attr_unused) { session_t *ps = session_ptr(w, vblank_timer); ev_timer_stop(EV_A_ w); - schedule_render(ps, true); + handle_end_of_vblank(ps); } static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { @@ -2435,7 +2463,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); ev_init(&ps->draw_timer, draw_callback); - ev_init(&ps->vblank_timer, schedule_render_callback); + ev_init(&ps->vblank_timer, vblank_callback); ev_init(&ps->fade_timer, fade_timer_callback); From 2e1c4e51b3b06c579040a03711a37a762b372de5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 5 Jul 2023 05:53:21 +0100 Subject: [PATCH 160/177] core: don't request vblank events when we are not rendering Previously everytime we receive a vblank event, we always request a new one. This made the logic somewhat simpler. But this generated many useless vblank events, and wasted power. We only need vblank events for two things: 1. after we rendered a frame, we need to know when it has been displayed on the screen. 2. estimating the refresh rate. This commit makes sure we only request vblank events when it's actually needed. Fixes #1079 Signed-off-by: Yuxuan Shui --- src/common.h | 3 ++ src/picom.c | 109 ++++++++++++++++++++++++++++++++--------------- src/statistics.h | 2 + src/x.c | 12 ++++++ src/x.h | 3 ++ 5 files changed, 94 insertions(+), 35 deletions(-) diff --git a/src/common.h b/src/common.h index ac9da4c733..0009f112a4 100644 --- a/src/common.h +++ b/src/common.h @@ -227,6 +227,9 @@ typedef struct session { bool first_frame; /// Whether screen has been turned off bool screen_is_off; + /// We asked X server to send us a event for the end of a vblank, and we haven't + /// received one yet. + bool vblank_event_requested; /// Event context for X Present extension. uint32_t present_event_id; xcb_special_event_t *present_event; diff --git a/src/picom.c b/src/picom.c index 75d914934a..71178fe9cc 100644 --- a/src/picom.c +++ b/src/picom.c @@ -258,6 +258,9 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { schedule: ps->render_in_progress = RENDER_QUEUED; ps->redraw_needed = false; + + x_request_vblank_event(ps, ps->last_msc + 1); + assert(!ev_is_active(&ps->draw_timer)); ev_timer_set(&ps->draw_timer, delay_s, 0); ev_timer_start(ps->loop, &ps->draw_timer); @@ -267,20 +270,43 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { /// /// Check if previously queued render has finished, and record the time it took. void handle_end_of_vblank(session_t *ps) { - if (ps->render_in_progress != RENDER_STARTED) { - // We didn't start rendering for this vblank, nothing to do + if (ps->render_in_progress == RENDER_IDLE) { + // We didn't start rendering for this vblank, no render time to record. + // But if we don't have a vblank estimate, we will ask for one more vblank + // event, so we can collect more data and get an estimate sooner. + if (render_statistics_get_vblank_time(&ps->render_stats) == 0) { + x_request_vblank_event(ps, ps->last_msc + 1); + } return; } - // We shouldn't have scheduled a render if the previous render hasn't been - // presented yet. - assert(!ev_is_active(&ps->draw_timer)); - + // render_in_progress is either RENDER_STARTED or RENDER_QUEUED struct timespec render_time; - bool completed = - ps->backend_data->ops->last_render_time(ps->backend_data, &render_time); + bool completed; + if (ps->render_in_progress == RENDER_STARTED) { + completed = + ps->backend_data->ops->last_render_time(ps->backend_data, &render_time); + } else { + completed = false; + } + + // Do we want to be notified when the next vblank comes? First, if frame_pacing is + // disabled, we don't need vblank events; or if the screen is off, we cannot + // request vblank events. Otherwise, we need vblank events in these cases: + // 1) if we know we need to redraw for the next vblank. + // 2) previous render hasn't completed yet, so it will be presented during the + // next vblank. we need to ask for an event for that. + // 3) if we don't have enough data for a vblank interval estimate, see above. + bool need_vblank_events = + ps->frame_pacing && (ps->redraw_needed || !completed || + render_statistics_get_vblank_time(&ps->render_stats) == 0); + + if (need_vblank_events) { + x_request_vblank_event(ps, ps->last_msc + 1); + } + if (!completed) { - // Render hasn't completed yet, keep render_in_progress as RENDER_STARTED + // Render hasn't completed yet, keep render_in_progress unchanged. log_debug("Last render did not complete during vblank, msc: %" PRIu64, ps->last_msc); return; @@ -1504,23 +1530,21 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ return; } - bool event_is_invalid = false; - if (ps->frame_pacing) { - auto next_msc = cne->msc + 1; - if (cne->msc <= ps->last_msc || cne->ust == 0) { - // X sometimes sends duplicate/bogus MSC events, don't - // use the msc value. Also ignore these events. - // - // See: - // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 - next_msc = ps->last_msc + 1; - event_is_invalid = true; - } - auto cookie = xcb_present_notify_msc( - ps->c.c, session_get_target_window(ps), 0, next_msc, 0, 0); - set_cant_fail_cookie(&ps->c, cookie); - } + assert(ps->frame_pacing); + assert(ps->vblank_event_requested); + ps->vblank_event_requested = false; + + // X sometimes sends duplicate/bogus MSC events, when screen has just been turned + // off. Don't use the msc value in these events. We treat this as not receiving a + // vblank event at all, and try to get a new one. + // + // See: + // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 + bool event_is_invalid = cne->msc <= ps->last_msc || cne->ust == 0; if (event_is_invalid) { + log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64, + cne->msc, cne->ust); + x_request_vblank_event(ps, ps->last_msc + 1); return; } @@ -1529,23 +1553,38 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ auto now_us = (int64_t)(now.tv_sec * 1000000L + now.tv_nsec / 1000); auto drift = iabs((int64_t)cne->ust - now_us); - if (ps->last_msc_instant != 0) { - auto frame_count = cne->msc - ps->last_msc; - int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); - render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time); - log_trace("Frame count %lu, frame time: %d us, rolling average: %u us, " - "msc: %" PRIu64 ", offset: %d us", - frame_count, frame_time, - render_statistics_get_vblank_time(&ps->render_stats), cne->ust, - (int)drift); - } else if (drift > 1000000 && ps->frame_pacing) { + if (ps->last_msc_instant == 0 && drift > 1000000) { // This is the first MSC event we receive, let's check if the timestamps // align with the monotonic clock. If not, disable frame pacing because we // can't schedule frames reliably. log_error("Temporal anomaly detected, frame pacing disabled. (Are we " "running inside a time namespace?), %" PRIi64 " %" PRIu64, now_us, ps->last_msc_instant); + if (ps->render_in_progress == RENDER_STARTED) { + // When frame_pacing is off, render_in_progress can't be + // RENDER_STARTED. See the comment on schedule_render(). + ps->render_in_progress = RENDER_IDLE; + } ps->frame_pacing = false; + return; + } + + if (ps->last_msc_instant != 0) { + auto frame_count = cne->msc - ps->last_msc; + int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); + if (frame_count == 1 && !ps->screen_is_off) { + render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time); + log_trace("Frame count %lu, frame time: %d us, rolling average: " + "%u us, " + "msc: %" PRIu64 ", offset: %d us", + frame_count, frame_time, + render_statistics_get_vblank_time(&ps->render_stats), + cne->ust, (int)drift); + } else { + log_trace("Frame count %lu, frame time: %d us, msc: %" PRIu64 + ", offset: %d us, not adding sample.", + frame_count, frame_time, cne->ust, (int)drift); + } } ps->last_msc_instant = cne->ust; ps->last_msc = cne->msc; diff --git a/src/statistics.h b/src/statistics.h index 67d02119af..42d7bc2d91 100644 --- a/src/statistics.h +++ b/src/statistics.h @@ -30,4 +30,6 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int unsigned int render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor); +/// Return the measured vblank interval in microseconds. Returns 0 if not enough +/// samples have been collected yet. unsigned int render_statistics_get_vblank_time(struct render_statistics *rs); diff --git a/src/x.c b/src/x.c index 1840ebb1fe..54cf702eca 100644 --- a/src/x.c +++ b/src/x.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -777,6 +778,17 @@ bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) { return false; } +void x_request_vblank_event(session_t *ps, uint64_t msc) { + if (ps->vblank_event_requested) { + return; + } + + auto cookie = + xcb_present_notify_msc(ps->c.c, session_get_target_window(ps), 0, msc, 0, 0); + set_cant_fail_cookie(&ps->c, cookie); + ps->vblank_event_requested = true; +} + /** * Convert a struct conv to a X picture convolution filter, normalizing the kernel * in the process. Allow the caller to specify the element at the center of the kernel, diff --git a/src/x.h b/src/x.h index b5bd1a59d3..39bf5ab0c1 100644 --- a/src/x.h +++ b/src/x.h @@ -419,3 +419,6 @@ void x_update_monitors(struct x_connection *, struct x_monitors *); void x_free_monitor_info(struct x_monitors *); uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); + +/// Ask X server to send us a notification for the next end of vblank. +void x_request_vblank_event(session_t *ps, uint64_t msc); From 23eb3d5f52b808f38386dc5848819a7c55f2a294 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 5 Jul 2023 05:54:44 +0100 Subject: [PATCH 161/177] core: don't unredir when display is turned off We unredirect because we receive bad vblank events, and also vblank events at a different interval compared to when the screen is on. But it is enough to just not record the vblank interval statistics when the screen is off. Although, unredirecting when display is off can also fix the problem where use-damage causes the screen to flicker when the display is turned off then back on. So we need something else for that. Signed-off-by: Yuxuan Shui --- src/common.h | 2 -- src/picom.c | 63 +++++----------------------------------------------- src/x.c | 20 +++++++++++++++++ src/x.h | 3 +++ 4 files changed, 28 insertions(+), 60 deletions(-) diff --git a/src/common.h b/src/common.h index 0009f112a4..889ca88328 100644 --- a/src/common.h +++ b/src/common.h @@ -150,8 +150,6 @@ typedef struct session { // === Event handlers === /// ev_io for X connection ev_io xiow; - /// Timer for checking DPMS power level - ev_timer dpms_check_timer; /// Timeout for delayed unredirection. ev_timer unredir_timer; /// Timer for fading diff --git a/src/picom.c b/src/picom.c index 71178fe9cc..2cfa08ecd7 100644 --- a/src/picom.c +++ b/src/picom.c @@ -122,27 +122,6 @@ static inline int64_t get_time_ms(void) { return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; } -static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { - // state is a bool indicating whether dpms is enabled - return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); -} - -void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { - auto ps = session_ptr(w, dpms_check_timer); - auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); - if (!r) { - log_fatal("Failed to query DPMS status."); - abort(); - } - auto now_screen_is_off = dpms_screen_is_off(r); - if (ps->screen_is_off != now_screen_is_off) { - log_debug("Screen is now %s", now_screen_is_off ? "off" : "on"); - ps->screen_is_off = now_screen_is_off; - queue_redraw(ps); - } - free(r); -} - /** * Find matched window. * @@ -329,18 +308,6 @@ void handle_end_of_vblank(session_t *ps) { } void queue_redraw(session_t *ps) { - if (ps->screen_is_off) { - // The screen is off, if there is a draw queued for the next frame (i.e. - // ps->redraw_needed == true), it won't be triggered until the screen is - // on again, because the abnormal Present events we will receive from the - // X server when the screen is off. Yet we need the draw_callback to be - // called as soon as possible so the screen can be unredirected. - // So here we unconditionally start the draw timer. - ev_timer_stop(ps->loop, &ps->draw_timer); - ev_timer_set(&ps->draw_timer, 0, 0); - ev_timer_start(ps->loop, &ps->draw_timer); - return; - } // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. // If --benchmark is used, redraw is always queued @@ -1047,19 +1014,6 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation, // If there's no window to paint, and the screen isn't redirected, // don't redirect it. unredir_possible = true; - } else if (ps->screen_is_off) { - // Screen is off, unredirect - // We do this unconditionally disregarding "unredir_if_possible" - // because it's important for correctness, because we need to - // workaround problems X server has around screen off. - // - // Known problems: - // 1. Sometimes OpenGL front buffer can lose content, and if we - // are doing partial updates (i.e. use-damage = true), the - // result will be wrong. - // 2. For frame pacing, X server sends bogus - // PresentCompleteNotify events when screen is off. - unredir_possible = true; } if (unredir_possible) { if (ps->redirected) { @@ -1569,6 +1523,8 @@ handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_ return; } + x_check_dpms_status(ps); + if (ps->last_msc_instant != 0) { auto frame_count = cne->msc - ps->last_msc; int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); @@ -2192,17 +2148,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ext_info = xcb_get_extension_data(ps->c.c, &xcb_dpms_id); ps->dpms_exists = ext_info && ext_info->present; - if (ps->dpms_exists) { - auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); - if (!r) { - log_fatal("Failed to query DPMS info"); - goto err; - } - ps->screen_is_off = dpms_screen_is_off(r); - // Check screen status every half second - ev_timer_init(&ps->dpms_check_timer, check_dpms_status, 0, 0.5); - ev_timer_start(ps->loop, &ps->dpms_check_timer); - free(r); + if (!ps->dpms_exists) { + log_fatal("No DPMS extension"); + exit(1); } // Parse configuration file @@ -2811,7 +2759,6 @@ static void session_destroy(session_t *ps) { // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); - ev_timer_stop(ps->loop, &ps->dpms_check_timer); ev_timer_stop(ps->loop, &ps->draw_timer); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal); diff --git a/src/x.c b/src/x.c index 54cf702eca..89786e18e3 100644 --- a/src/x.c +++ b/src/x.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -789,6 +790,25 @@ void x_request_vblank_event(session_t *ps, uint64_t msc) { ps->vblank_event_requested = true; } +static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { + // state is a bool indicating whether dpms is enabled + return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); +} + +void x_check_dpms_status(session_t *ps) { + auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); + if (!r) { + log_fatal("Failed to query DPMS status."); + abort(); + } + auto now_screen_is_off = dpms_screen_is_off(r); + if (ps->screen_is_off != now_screen_is_off) { + log_debug("Screen is now %s", now_screen_is_off ? "off" : "on"); + ps->screen_is_off = now_screen_is_off; + } + free(r); +} + /** * Convert a struct conv to a X picture convolution filter, normalizing the kernel * in the process. Allow the caller to specify the element at the center of the kernel, diff --git a/src/x.h b/src/x.h index 39bf5ab0c1..60bcfef492 100644 --- a/src/x.h +++ b/src/x.h @@ -422,3 +422,6 @@ uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); /// Ask X server to send us a notification for the next end of vblank. void x_request_vblank_event(session_t *ps, uint64_t msc); + +/// Update ps->screen_is_off to reflect the current DPMS state. +void x_check_dpms_status(session_t *ps); From 147561a313687ccf62fe10fea9b6cebcec8990b2 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 03:04:59 +0000 Subject: [PATCH 162/177] config: add a debug environment variable This is where we keep temporary, short living, private debug options. Adding and removing command line and config file options are troublesome, and we don't want people adding these to their config files. Signed-off-by: Yuxuan Shui --- src/config.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/config.h | 7 ++++++ 2 files changed, 75 insertions(+) diff --git a/src/config.c b/src/config.c index ec39aa4390..efbf279f7d 100644 --- a/src/config.c +++ b/src/config.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -615,6 +616,72 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc return ret; } +struct debug_options_entry { + const char *name; + const char **choices; + size_t offset; +}; + +static const struct debug_options_entry debug_options_entries[] = { + +}; + +void parse_debug_option_single(char *setting, struct debug_options *debug_options) { + char *equal = strchr(setting, '='); + size_t name_len = equal ? (size_t)(equal - setting) : strlen(setting); + for (size_t i = 0; i < ARR_SIZE(debug_options_entries); i++) { + if (strncmp(setting, debug_options_entries[i].name, name_len) != 0) { + continue; + } + if (debug_options_entries[i].name[name_len] != '\0') { + continue; + } + auto value = (int *)((void *)debug_options + debug_options_entries[i].offset); + if (equal) { + const char *const arg = equal + 1; + if (debug_options_entries[i].choices != NULL) { + for (size_t j = 0; debug_options_entries[i].choices[j]; j++) { + if (strcmp(arg, debug_options_entries[i].choices[j]) == + 0) { + *value = (int)j; + return; + } + } + } + if (!parse_int(arg, value)) { + log_error("Invalid value for debug option %s: %s, it " + "will be ignored.", + debug_options_entries[i].name, arg); + } + } else if (debug_options_entries[i].choices == NULL) { + *value = 1; + } else { + log_error( + "Missing value for debug option %s, it will be ignored.", setting); + } + return; + } + log_error("Invalid debug option: %s", setting); +} + +/// Parse debug options from environment variable `PICOM_DEBUG`. +void parse_debug_options(struct debug_options *debug_options) { + const char *debug = getenv("PICOM_DEBUG"); + const struct debug_options default_debug_options = {}; + + *debug_options = default_debug_options; + if (!debug) { + return; + } + + scoped_charp debug_copy = strdup(debug); + char *tmp, *needle = strtok_r(debug_copy, ";", &tmp); + while (needle) { + parse_debug_option_single(needle, debug_options); + needle = strtok_r(NULL, ";", &tmp); + } +} + /** * Parse a list of window shader rules. */ @@ -817,5 +884,6 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, (void)hasneg; (void)winopt_mask; #endif + parse_debug_options(&opt->debug_options); return ret; } diff --git a/src/config.h b/src/config.h index 31e6774c8f..93bede8420 100644 --- a/src/config.h +++ b/src/config.h @@ -73,6 +73,11 @@ enum blur_method { typedef struct _c2_lptr c2_lptr_t; +/// Internal, private options for debugging and development use. +struct debug_options { + +}; + /// Structure representing all options. typedef struct options { // === Debugging === @@ -262,6 +267,8 @@ typedef struct options { c2_lptr_t *transparent_clipping_blacklist; bool dithered_present; + + struct debug_options debug_options; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; From 1430d281161fcaff7bd1807f861c34c7a287a100 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 9 Jul 2023 16:39:44 +0100 Subject: [PATCH 163/177] core: refactor frame pacing Factored out vblank event generation, add abstraction over how vblank events are generated. The goal is so we can request vblank events in different ways based on the driver we are running on. Tried to simplify the frame scheduling logic, we will see if I succeeded or not. Also, the logic to exclude vblank events for vblank interval estimation when the screen off is dropped. It's too hard to get right, we need to find something robust. Signed-off-by: Yuxuan Shui --- src/common.h | 41 ++--- src/config.h | 10 + src/meson.build | 3 +- src/picom.c | 472 +++++++++++++++++++++--------------------------- src/vblank.c | 257 ++++++++++++++++++++++++++ src/vblank.h | 45 +++++ src/x.c | 24 +-- src/x.h | 8 +- 8 files changed, 547 insertions(+), 313 deletions(-) create mode 100644 src/vblank.c create mode 100644 src/vblank.h diff --git a/src/common.h b/src/common.h index 889ca88328..52967e600d 100644 --- a/src/common.h +++ b/src/common.h @@ -134,17 +134,6 @@ struct shader_info { UT_hash_handle hh; }; -enum render_progress { - /// Render is finished and presented to the screen. - RENDER_IDLE = 0, - /// Rendering is queued, but not started yet. - RENDER_QUEUED, - /// Backend has been called, render commands have been issued. - RENDER_STARTED, - /// Backend reported render commands have been finished. (not actually used). - RENDER_FINISHED, -}; - /// Structure containing all necessary data for a session. typedef struct session { // === Event handlers === @@ -156,8 +145,6 @@ typedef struct session { ev_timer fade_timer; /// Use an ev_timer callback for drawing ev_timer draw_timer; - /// Timer for the end of each vblanks. Used for calling schedule_render. - ev_timer vblank_timer; /// Called every time we have timeouts or new data on socket, /// so we can be sure if xcb read from X socket at anytime during event /// handling, we will not left any event unhandled in the queue @@ -225,12 +212,6 @@ typedef struct session { bool first_frame; /// Whether screen has been turned off bool screen_is_off; - /// We asked X server to send us a event for the end of a vblank, and we haven't - /// received one yet. - bool vblank_event_requested; - /// Event context for X Present extension. - uint32_t present_event_id; - xcb_special_event_t *present_event; /// When last MSC event happened, in useconds. uint64_t last_msc_instant; /// The last MSC number @@ -242,6 +223,8 @@ typedef struct session { uint64_t next_render; /// Whether we can perform frame pacing. bool frame_pacing; + /// Vblank event scheduler + struct vblank_scheduler *vblank_scheduler; /// Render statistics struct render_statistics render_stats; @@ -253,14 +236,18 @@ typedef struct session { options_t o; /// Whether we have hit unredirection timeout. bool tmout_unredir_hit; - /// Rendering is currently in progress. This means we are in any stage of - /// rendering a frame. The render could be queued but not yet started, or it could - /// have finished but not yet presented. - enum render_progress render_in_progress; - /// Whether there are changes pending for the next render. A render is currently - /// in progress, otherwise we would have started a new render instead of setting - /// this flag. - bool redraw_needed; + /// If the backend is busy. This means two things: + /// Either the backend is currently rendering a frame, or a frame has been + /// rendered but has yet to be presented. In either case, we should not start + /// another render right now. As if we start issuing rendering commands now, we + /// will have to wait for either the the current render to finish, or the current + /// back buffer to be become available again. In either case, we will be wasting + /// time. + bool backend_busy; + /// Whether a render is queued. This generally means there are pending updates + /// to the screen that's neither included in the current render, nor on the + /// screen. + bool render_queued; /// Cache a xfixes region so we don't need to allocate it every time. /// A workaround for yshui/picom#301 diff --git a/src/config.h b/src/config.h index 93bede8420..bdf384aca7 100644 --- a/src/config.h +++ b/src/config.h @@ -73,6 +73,16 @@ enum blur_method { typedef struct _c2_lptr c2_lptr_t; +enum vblank_scheduler_type { + /// X Present extension based vblank events + VBLANK_SCHEDULER_PRESENT, + /// GLX_SGI_video_sync based vblank events + VBLANK_SCHEDULER_SGI_VIDEO_SYNC, + /// An invalid scheduler, served as a scheduler count, and + /// as a sentinel value. + LAST_VBLANK_SCHEDULER, +}; + /// Internal, private options for debugging and development use. struct debug_options { diff --git a/src/meson.build b/src/meson.build index a608c57538..51f96488cd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,7 +9,8 @@ base_deps = [ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', - 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c') ] + 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c', + 'vblank.c') ] picom_inc = include_directories('.') cflags = [] diff --git a/src/picom.c b/src/picom.c index 2cfa08ecd7..4c75329d20 100644 --- a/src/picom.c +++ b/src/picom.c @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -64,6 +66,7 @@ #include "options.h" #include "statistics.h" #include "uthash_extra.h" +#include "vblank.h" /// Get session_t pointer from a pointer to a member of session_t #define session_ptr(ptr, member) \ @@ -142,43 +145,158 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t return w; } +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); + + // 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 %lu, frame time: %d us, ust: " + "%" PRIu64 "", + frame_count, frame_time, e->ust); + } else { + log_trace("Frame count %lu, 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); + } + + 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; +} +/// vblank callback scheduled by schedule_render. +/// +/// Check if previously queued render has finished, and record the time it took. +enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, void *ud) { + auto ps = (session_t *)ud; + assert(ps->frame_pacing); + assert(ps->backend_busy); + assert(ps->render_queued); + assert(ps->vblank_scheduler); + + collect_vblank_interval_statistics(e, ud); + + 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. + 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->last_schedule_delay = 0; + ps->backend_busy = false; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; + double delay_s = 0; + if (ps->next_render > now_us) { + delay_s = (double)(ps->next_render - now_us) / 1000000.0; + } + log_verbose("Prepare to start rendering: delay: %f s, next_render: %" PRIu64 + ", now_us: %" PRIu64, + delay_s, ps->next_render, now_us); + assert(!ev_is_active(&ps->draw_timer)); + ev_timer_set(&ps->draw_timer, delay_s, 0); + ev_timer_start(ps->loop, &ps->draw_timer); + 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_in_progress == RENDER_IDLE. -/// 2. then, we need to figure out the best time to start rendering. first, we need to -/// know when the current vblank will end. we have this information from the Present -/// extension: we know when was the end of last vblank, and we know the refresh rate. -/// so we can calculate the end of the current vblank. if our render time estimation -/// shows we could miss that target, we push the target back an integer number of -/// frames. and we calculate the end of the target vblank similarly. -/// 3. We schedule a render for that target. we use past statistics about how long our -/// renders took to figure out when to start rendering. we start rendering as late as -/// possible, but not too late that we miss the target vblank. render_in_progress is -/// set to RENDER_QUEUED. -/// 4. draw_callback() is called at the schedule time. Backend APIs are called to issue -/// render commands. render_in_progress is set to RENDER_STARTED. -/// 5. PresentCompleteNotify is received, which gives us the actual time when the current -/// vblank will end/ended. We schedule a call to handle_end_of_vblank at the -/// appropriate time. -/// 6. in handle_end_of_vblank, we check the backend to see if the render has finished. if -/// not, render_in_progress is unchanged; otherwise, render_in_progress is set to -/// RENDER_IDLE, and the next frame can be scheduled. +/// 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 current vblank will start, as we can't start render +/// before the current rendered frame is diplayed 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. /// -/// This 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. +/// There is a small caveat in step 2. As 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. /// -/// 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. - +/// 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) { // 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. @@ -228,97 +346,38 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { delay_s, render_budget, frame_time, now_us, deadline); } - log_trace("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, frame_time: " - "%" PRIu32 ", now_us: %" PRIu64 ", next_msc: %" PRIu64 ", " - "divisor: %d", - delay_s, ps->last_msc_instant, render_budget, frame_time, now_us, - deadline, divisor); + log_verbose("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, " + "frame_time: " + "%" PRIu32 ", now_us: %" PRIu64 ", next_msc: %" PRIu64 ", " + "divisor: %d", + delay_s, ps->last_msc_instant, render_budget, frame_time, now_us, + deadline, divisor); schedule: - ps->render_in_progress = RENDER_QUEUED; - ps->redraw_needed = false; - - x_request_vblank_event(ps, ps->last_msc + 1); - - assert(!ev_is_active(&ps->draw_timer)); - ev_timer_set(&ps->draw_timer, delay_s, 0); - ev_timer_start(ps->loop, &ps->draw_timer); -} - -/// Called after a vblank has ended -/// -/// Check if previously queued render has finished, and record the time it took. -void handle_end_of_vblank(session_t *ps) { - if (ps->render_in_progress == RENDER_IDLE) { - // We didn't start rendering for this vblank, no render time to record. - // But if we don't have a vblank estimate, we will ask for one more vblank - // event, so we can collect more data and get an estimate sooner. - if (render_statistics_get_vblank_time(&ps->render_stats) == 0) { - x_request_vblank_event(ps, ps->last_msc + 1); - } - return; - } - - // render_in_progress is either RENDER_STARTED or RENDER_QUEUED - struct timespec render_time; - bool completed; - if (ps->render_in_progress == RENDER_STARTED) { - completed = - ps->backend_data->ops->last_render_time(ps->backend_data, &render_time); + ps->render_queued = true; + // If the backend is not busy, we just need to schedule the render at the + // specified time; otherwise we need to wait for vblank events. + if (!ps->backend_busy) { + assert(!ev_is_active(&ps->draw_timer)); + ev_timer_set(&ps->draw_timer, delay_s, 0); + ev_timer_start(ps->loop, &ps->draw_timer); } else { - completed = false; - } - - // Do we want to be notified when the next vblank comes? First, if frame_pacing is - // disabled, we don't need vblank events; or if the screen is off, we cannot - // request vblank events. Otherwise, we need vblank events in these cases: - // 1) if we know we need to redraw for the next vblank. - // 2) previous render hasn't completed yet, so it will be presented during the - // next vblank. we need to ask for an event for that. - // 3) if we don't have enough data for a vblank interval estimate, see above. - bool need_vblank_events = - ps->frame_pacing && (ps->redraw_needed || !completed || - render_statistics_get_vblank_time(&ps->render_stats) == 0); - - if (need_vblank_events) { - x_request_vblank_event(ps, ps->last_msc + 1); - } - - if (!completed) { - // Render hasn't completed yet, keep render_in_progress unchanged. - log_debug("Last render did not complete during vblank, msc: %" PRIu64, - ps->last_msc); - return; - } - - int render_time_us = - (int)(render_time.tv_sec * 1000000L + render_time.tv_nsec / 1000L); - // The frame has been finished and presented, record its render time. - log_trace("Last render call took: %d (gpu) + %d (cpu) us, " - "last_msc: %" PRIu64, - render_time_us, (int)ps->last_schedule_delay, ps->last_msc); - render_statistics_add_render_time_sample( - &ps->render_stats, render_time_us + (int)ps->last_schedule_delay); - ps->last_schedule_delay = 0; - ps->render_in_progress = RENDER_IDLE; - - if (ps->redraw_needed) { - schedule_render(ps, true); + // We should never set backend_busy to true unless frame_pacing is + // enabled. + assert(ps->vblank_scheduler); + if (!vblank_scheduler_schedule(ps->vblank_scheduler, + schedule_render_at_vblank, ps)) { + // TODO(yshui): handle error here + abort(); + } } } void queue_redraw(session_t *ps) { - // Whether we have already rendered for the current frame. - // If frame pacing is not enabled, pretend this is false. - // If --benchmark is used, redraw is always queued - if (ps->render_in_progress == RENDER_IDLE && !ps->o.benchmark) { - schedule_render(ps, false); - } else if (ps->render_in_progress > RENDER_QUEUED) { - // render_in_progress > RENDER_QUEUED means we have already issued the - // render commands, so a new render must be scheduled to reflect new - // changes. Otherwise the queued render will include1 the new changes. - ps->redraw_needed = true; + if (ps->render_queued) { + return; } + schedule_render(ps, false); } /** @@ -1395,23 +1454,19 @@ static bool redirect_start(session_t *ps) { } if (ps->present_exists && ps->frame_pacing) { - ps->present_event_id = x_new_id(&ps->c); - auto select_input = xcb_present_select_input( - ps->c.c, ps->present_event_id, session_get_target_window(ps), - XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); - auto notify_msc = xcb_present_notify_msc( - ps->c.c, session_get_target_window(ps), 0, 0, 1, 0); - set_cant_fail_cookie(&ps->c, select_input); - set_cant_fail_cookie(&ps->c, notify_msc); - ps->present_event = xcb_register_for_special_xge( - ps->c.c, &xcb_present_id, ps->present_event_id, NULL); - // Initialize rendering and frame timing statistics, and frame pacing // states. ps->last_msc_instant = 0; ps->last_msc = 0; ps->last_schedule_delay = 0; render_statistics_reset(&ps->render_stats); + ps->vblank_scheduler = + vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps)); + if (!ps->vblank_scheduler) { + return false; + } + vblank_scheduler_schedule(ps->vblank_scheduler, + collect_vblank_interval_statistics, ps); } else if (ps->frame_pacing) { log_error("Present extension is not supported, frame pacing disabled."); ps->frame_pacing = false; @@ -1459,12 +1514,9 @@ static void unredirect(session_t *ps) { free(ps->damage_ring); ps->damage_ring = ps->damage = NULL; - if (ps->present_event_id) { - xcb_present_select_input(ps->c.c, ps->present_event_id, - session_get_target_window(ps), 0); - ps->present_event_id = XCB_NONE; - xcb_unregister_for_special_event(ps->c.c, ps->present_event); - ps->present_event = NULL; + if (ps->vblank_scheduler) { + vblank_scheduler_free(ps->vblank_scheduler); + ps->vblank_scheduler = NULL; } // Must call XSync() here @@ -1474,122 +1526,12 @@ static void unredirect(session_t *ps) { log_debug("Screen unredirected."); } -/// Handle PresentCompleteNotify events -/// -/// Record the MSC value and their timestamps, and schedule handle_end_of_vblank() at the -/// correct time. -static void -handle_present_complete_notify(session_t *ps, xcb_present_complete_notify_event_t *cne) { - if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) { - return; - } - - assert(ps->frame_pacing); - assert(ps->vblank_event_requested); - ps->vblank_event_requested = false; - - // X sometimes sends duplicate/bogus MSC events, when screen has just been turned - // off. Don't use the msc value in these events. We treat this as not receiving a - // vblank event at all, and try to get a new one. - // - // See: - // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 - bool event_is_invalid = cne->msc <= ps->last_msc || cne->ust == 0; - if (event_is_invalid) { - log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64, - cne->msc, cne->ust); - x_request_vblank_event(ps, ps->last_msc + 1); - return; - } - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - auto now_us = (int64_t)(now.tv_sec * 1000000L + now.tv_nsec / 1000); - auto drift = iabs((int64_t)cne->ust - now_us); - - if (ps->last_msc_instant == 0 && drift > 1000000) { - // This is the first MSC event we receive, let's check if the timestamps - // align with the monotonic clock. If not, disable frame pacing because we - // can't schedule frames reliably. - log_error("Temporal anomaly detected, frame pacing disabled. (Are we " - "running inside a time namespace?), %" PRIi64 " %" PRIu64, - now_us, ps->last_msc_instant); - if (ps->render_in_progress == RENDER_STARTED) { - // When frame_pacing is off, render_in_progress can't be - // RENDER_STARTED. See the comment on schedule_render(). - ps->render_in_progress = RENDER_IDLE; - } - ps->frame_pacing = false; - return; - } - - x_check_dpms_status(ps); - - if (ps->last_msc_instant != 0) { - auto frame_count = cne->msc - ps->last_msc; - int frame_time = (int)((cne->ust - ps->last_msc_instant) / frame_count); - if (frame_count == 1 && !ps->screen_is_off) { - render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time); - log_trace("Frame count %lu, frame time: %d us, rolling average: " - "%u us, " - "msc: %" PRIu64 ", offset: %d us", - frame_count, frame_time, - render_statistics_get_vblank_time(&ps->render_stats), - cne->ust, (int)drift); - } else { - log_trace("Frame count %lu, frame time: %d us, msc: %" PRIu64 - ", offset: %d us, not adding sample.", - frame_count, frame_time, cne->ust, (int)drift); - } - } - ps->last_msc_instant = cne->ust; - ps->last_msc = cne->msc; - // Note we can't update ps->render_in_progress here because of this present - // complete notify, as we don't know if the render finished before the end of - // vblank or not. We schedule a call to handle_end_of_vblank() to figure out if we - // are still rendering, and update ps->render_in_progress accordingly. - if (now_us > (int64_t)cne->ust) { - handle_end_of_vblank(ps); - } else { - // Wait until the end of the current vblank to call - // handle_end_of_vblank. If we call it too early, it can - // mistakenly think the render missed the vblank, and doesn't - // schedule render for the next vblank, causing frame drops. - log_trace("The end of this vblank is %" PRIi64 " us into the " - "future", - (int64_t)cne->ust - now_us); - assert(!ev_is_active(&ps->vblank_timer)); - ev_timer_set(&ps->vblank_timer, - ((double)cne->ust - (double)now_us) / 1000000.0, 0); - ev_timer_start(ps->loop, &ps->vblank_timer); - } -} - -static void handle_present_events(session_t *ps) { - if (!ps->present_event) { - // Screen not redirected - return; - } - xcb_present_generic_event_t *ev; - while ((ev = (void *)xcb_poll_for_special_event(ps->c.c, ps->present_event))) { - if (ev->event != ps->present_event_id) { - // This event doesn't have the right event context, it's not meant - // for us. - goto next; - } - - // We only subscribed to the complete notify event. - assert(ev->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY); - handle_present_complete_notify(ps, (void *)ev); - next: - free(ev); - } -} - // Handle queued events before we go to sleep static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) { session_t *ps = session_ptr(w, event_check); - handle_present_events(ps); + if (ps->vblank_scheduler) { + vblank_handle_x_events(ps->vblank_scheduler); + } xcb_generic_event_t *ev; while ((ev = xcb_poll_for_queued_event(ps->c.c))) { @@ -1711,6 +1653,9 @@ static void handle_pending_updates(EV_P_ struct session *ps) { } static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { + assert(!ps->backend_busy); + assert(ps->render_queued); + struct timespec now; int64_t draw_callback_enter_us; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1801,13 +1746,13 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { if (ps->redirected && ps->o.stoppaint_force != ON) { static int paint = 0; - log_trace("Render start, frame %d", paint); + log_verbose("Render start, frame %d", paint); if (!ps->o.legacy_backends) { did_render = paint_all_new(ps, bottom); } else { paint_all(ps, bottom); } - log_trace("Render end"); + log_verbose("Render end"); ps->first_frame = false; paint++; @@ -1816,30 +1761,30 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { } } - if (ps->frame_pacing && did_render) { - ps->render_in_progress = RENDER_STARTED; - } else { - // With frame pacing, we set render_in_progress to RENDER_IDLE after the - // end of vblank. Without frame pacing, we won't be receiving vblank - // events, so we set render_in_progress to RENDER_IDLE here, right after - // we issue the render commands. - // The other case is if we decided there is no change to render, in that - // case no render command is issued, so we also set render_in_progress to - // RENDER_IDLE. - ps->render_in_progress = RENDER_IDLE; - } + // With frame pacing, we set backend_busy to true after the end of + // vblank. Without frame pacing, we won't be receiving vblank events, so + // we set backend_busy to false here, right after we issue the render + // commands. + // The other case is if we decided there is no change to render, in that + // case no render command is issued, so we also set backend_busy to + // false. + ps->backend_busy = (ps->frame_pacing && did_render); ps->next_render = 0; if (!fade_running) { ps->fade_time = 0L; } + ps->render_queued = false; + // TODO(yshui) Investigate how big the X critical section needs to be. There are // suggestions that rendering should be in the critical section as well. // Queue redraw if animation is running. This should be picked up by next present // event. - ps->redraw_needed = animation; + if (animation) { + queue_redraw(ps); + } } static void draw_callback(EV_P_ ev_timer *w, int revents) { @@ -1855,13 +1800,6 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) { } } -static void vblank_callback(EV_P_ ev_timer *w, int revents attr_unused) { - session_t *ps = session_ptr(w, vblank_timer); - ev_timer_stop(EV_A_ w); - - handle_end_of_vblank(ps); -} - static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) { session_t *ps = (session_t *)w; xcb_generic_event_t *ev = xcb_poll_for_event(ps->c.c); @@ -2013,7 +1951,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .randr_exists = 0, .randr_event = 0, .randr_error = 0, - .present_event_id = XCB_NONE, .glx_exists = false, .glx_event = 0, .glx_error = 0, @@ -2450,7 +2387,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_io_start(ps->loop, &ps->xiow); ev_init(&ps->unredir_timer, tmout_unredir_callback); ev_init(&ps->draw_timer, draw_callback); - ev_init(&ps->vblank_timer, vblank_callback); ev_init(&ps->fade_timer, fade_timer_callback); diff --git a/src/vblank.c b/src/vblank.c new file mode 100644 index 0000000000..74420ace59 --- /dev/null +++ b/src/vblank.c @@ -0,0 +1,257 @@ +#include + +#include +#include +#include +#include +#include + +#include "compiler.h" +#include "config.h" +#include "list.h" // for container_of +#include "log.h" +#include "vblank.h" +#include "x.h" + +struct vblank_closure { + vblank_callback_t fn; + void *user_data; +}; + +struct vblank_scheduler { + struct x_connection *c; + size_t callback_capacity, callback_count; + struct vblank_closure *callbacks; + struct ev_loop *loop; + xcb_window_t target_window; + enum vblank_scheduler_type type; + bool vblank_event_requested; +}; + +struct present_vblank_scheduler { + struct vblank_scheduler base; + + uint64_t last_msc; + /// The timestamp for the end of last vblank. + uint64_t last_ust; + ev_timer callback_timer; + xcb_present_event_t event_id; + xcb_special_event_t *event; +}; + +struct vblank_scheduler_ops { + void (*init)(struct vblank_scheduler *self); + void (*deinit)(struct vblank_scheduler *self); + void (*schedule)(struct vblank_scheduler *self); + bool (*handle_x_events)(struct vblank_scheduler *self); +}; + +static void +vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event); + +static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; + log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64, + base->target_window, self->last_msc + 1); + assert(!base->vblank_event_requested); + x_request_vblank_event(base->c, base->target_window, self->last_msc + 1); + base->vblank_event_requested = true; +} + +static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) { + auto sched = container_of(w, struct present_vblank_scheduler, callback_timer); + auto event = (struct vblank_event){ + .msc = sched->last_msc, + .ust = sched->last_ust, + }; + sched->base.vblank_event_requested = false; + vblank_scheduler_invoke_callbacks(&sched->base, &event); +} + +static void present_vblank_scheduler_init(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; + base->type = VBLANK_SCHEDULER_PRESENT; + ev_timer_init(&self->callback_timer, present_vblank_callback, 0, 0); + + self->event_id = x_new_id(base->c); + auto select_input = + xcb_present_select_input(base->c->c, self->event_id, base->target_window, + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); + set_cant_fail_cookie(base->c, select_input); + self->event = + xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL); +} + +static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; + ev_timer_stop(base->loop, &self->callback_timer); + auto select_input = + xcb_present_select_input(base->c->c, self->event_id, base->target_window, 0); + set_cant_fail_cookie(base->c, select_input); + xcb_unregister_for_special_event(base->c->c, self->event); +} + +/// Handle PresentCompleteNotify events +/// +/// Schedule the registered callback to be called when the current vblank ends. +static void handle_present_complete_notify(struct present_vblank_scheduler *self, + xcb_present_complete_notify_event_t *cne) { + assert(self->base.type == VBLANK_SCHEDULER_PRESENT); + + if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) { + return; + } + + assert(self->base.vblank_event_requested); + + // X sometimes sends duplicate/bogus MSC events, when screen has just been turned + // off. Don't use the msc value in these events. We treat this as not receiving a + // vblank event at all, and try to get a new one. + // + // See: + // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418 + bool event_is_invalid = cne->msc <= self->last_msc || cne->ust == 0; + if (event_is_invalid) { + log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64, + cne->msc, cne->ust); + x_request_vblank_event(self->base.c, cne->window, self->last_msc + 1); + return; + } + + self->last_ust = cne->ust; + self->last_msc = cne->msc; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000); + double delay_sec = 0.0; + if (now_us < cne->ust) { + log_trace("The end of this vblank is %lu us into the " + "future", + cne->ust - now_us); + delay_sec = (double)(cne->ust - now_us) / 1000000.0; + } + // Wait until the end of the current vblank to invoke callbacks. If we + // call it too early, it can mistakenly think the render missed the + // vblank, and doesn't schedule render for the next vblank, causing frame + // drops. + assert(!ev_is_active(&self->callback_timer)); + ev_timer_set(&self->callback_timer, delay_sec, 0); + ev_timer_start(self->base.loop, &self->callback_timer); +} + +static bool handle_present_events(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; + xcb_present_generic_event_t *ev; + while ((ev = (void *)xcb_poll_for_special_event(base->c->c, self->event))) { + if (ev->event != self->event_id) { + // This event doesn't have the right event context, it's not meant + // for us. + goto next; + } + + // We only subscribed to the complete notify event. + assert(ev->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY); + handle_present_complete_notify(self, (void *)ev); + next: + free(ev); + } + return true; +} + +static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = { + [VBLANK_SCHEDULER_PRESENT] = + { + .init = present_vblank_scheduler_init, + .deinit = present_vblank_scheduler_deinit, + .schedule = present_vblank_scheduler_schedule, + .handle_x_events = handle_present_events, + }, +}; + +static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].schedule; + assert(fn != NULL); + fn(self); +} + +bool vblank_scheduler_schedule(struct vblank_scheduler *self, + vblank_callback_t vblank_callback, void *user_data) { + if (self->callback_count == 0) { + vblank_scheduler_schedule_internal(self); + } + if (self->callback_count == self->callback_capacity) { + size_t new_capacity = + self->callback_capacity ? self->callback_capacity * 2 : 1; + void *new_buffer = + realloc(self->callbacks, new_capacity * sizeof(*self->callbacks)); + if (!new_buffer) { + return false; + } + self->callbacks = new_buffer; + self->callback_capacity = new_capacity; + } + self->callbacks[self->callback_count++] = (struct vblank_closure){ + .fn = vblank_callback, + .user_data = user_data, + }; + return true; +} + +static void +vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) { + // callbacks might be added during callback invocation, so we need to + // copy the callback_count. + size_t count = self->callback_count, write_head = 0; + for (size_t i = 0; i < count; i++) { + auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data); + switch (action) { + case VBLANK_CALLBACK_AGAIN: + if (i != write_head) { + self->callbacks[write_head] = self->callbacks[i]; + } + write_head++; + case VBLANK_CALLBACK_DONE: + default: // nothing to do + break; + } + } + memset(self->callbacks + write_head, 0, + (count - write_head) * sizeof(*self->callbacks)); + assert(count == self->callback_count && "callbacks should not be added when " + "callbacks are being invoked."); + self->callback_count = write_head; + if (self->callback_count) { + vblank_scheduler_schedule_internal(self); + } +} + +void vblank_scheduler_free(struct vblank_scheduler *self) { + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].deinit; + if (fn != NULL) { + fn(self); + } + free(self->callbacks); + free(self); +} + +struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, + xcb_window_t target_window) { + struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler)); + self->target_window = target_window; + self->c = c; + self->loop = loop; + vblank_scheduler_ops[VBLANK_SCHEDULER_PRESENT].init(self); + return self; +} + +bool vblank_handle_x_events(struct vblank_scheduler *self) { + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].handle_x_events; + if (fn != NULL) { + return fn(self); + } + return true; +} \ No newline at end of file diff --git a/src/vblank.h b/src/vblank.h new file mode 100644 index 0000000000..a5073456c2 --- /dev/null +++ b/src/vblank.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "x.h" + +/// An object that schedule vblank events. +struct vblank_scheduler; + +struct vblank_event { + uint64_t msc; + uint64_t ust; +}; + +enum vblank_callback_action { + /// The callback should be called again in the next vblank. + VBLANK_CALLBACK_AGAIN, + /// The callback is done and should not be called again. + VBLANK_CALLBACK_DONE, +}; + +typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event, + void *user_data); + +/// Schedule a vblank event. +/// +/// Schedule for `cb` to be called when the current vblank ends. If this is called +/// from a callback function for the current vblank, the newly scheduled callback +/// will be called in the next vblank. +/// +/// Returns whether the scheduling is successful. Scheduling can fail if there +/// is not enough memory. +bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb, + void *user_data); +struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, + xcb_window_t target_window); +void vblank_scheduler_free(struct vblank_scheduler *); + +bool vblank_handle_x_events(struct vblank_scheduler *self); diff --git a/src/x.c b/src/x.c index 89786e18e3..45abe31197 100644 --- a/src/x.c +++ b/src/x.c @@ -779,15 +779,10 @@ bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) { return false; } -void x_request_vblank_event(session_t *ps, uint64_t msc) { - if (ps->vblank_event_requested) { - return; - } - +void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc) { auto cookie = - xcb_present_notify_msc(ps->c.c, session_get_target_window(ps), 0, msc, 0, 0); - set_cant_fail_cookie(&ps->c, cookie); - ps->vblank_event_requested = true; + xcb_present_notify_msc(c->c, window, 0, msc, 0, 0); + set_cant_fail_cookie(c, cookie); } static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { @@ -795,18 +790,19 @@ static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); } -void x_check_dpms_status(session_t *ps) { - auto r = xcb_dpms_info_reply(ps->c.c, xcb_dpms_info(ps->c.c), NULL); +bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off) { + auto r = xcb_dpms_info_reply(c->c, xcb_dpms_info(c->c), NULL); if (!r) { - log_fatal("Failed to query DPMS status."); - abort(); + log_error("Failed to query DPMS status."); + return false; } auto now_screen_is_off = dpms_screen_is_off(r); - if (ps->screen_is_off != now_screen_is_off) { + if (*screen_is_off != now_screen_is_off) { log_debug("Screen is now %s", now_screen_is_off ? "off" : "on"); - ps->screen_is_off = now_screen_is_off; + *screen_is_off = now_screen_is_off; } free(r); + return true; } /** diff --git a/src/x.h b/src/x.h index 60bcfef492..df45b5cca7 100644 --- a/src/x.h +++ b/src/x.h @@ -421,7 +421,9 @@ void x_free_monitor_info(struct x_monitors *); uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); /// Ask X server to send us a notification for the next end of vblank. -void x_request_vblank_event(session_t *ps, uint64_t msc); +void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc); -/// Update ps->screen_is_off to reflect the current DPMS state. -void x_check_dpms_status(session_t *ps); +/// Update screen_is_off to reflect the current DPMS state. +/// +/// Returns true if the DPMS state was successfully queried, false otherwise. +bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off); From 4b57e3aa4c1c1c4c48bf36ce01f1fc33130c07c1 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 23:07:32 +0000 Subject: [PATCH 164/177] config: add debug options to enable timing based pacing Disable timing estimation based pacing by default, as it might not work well across drivers, and might have subtle bugs. You can try setting `PICOM_DEBUG=smart_frame_pacing` if you want to try it out. Signed-off-by: Yuxuan Shui --- src/config.c | 5 ++++- src/config.h | 4 +++- src/picom.c | 25 ++++++++++++++++++------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/config.c b/src/config.c index efbf279f7d..8c1ca91d94 100644 --- a/src/config.c +++ b/src/config.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -623,7 +624,9 @@ struct debug_options_entry { }; static const struct debug_options_entry debug_options_entries[] = { - + "smart_frame_pacing", + NULL, + offsetof(struct debug_options, smart_frame_pacing), }; void parse_debug_option_single(char *setting, struct debug_options *debug_options) { diff --git a/src/config.h b/src/config.h index bdf384aca7..ac3b6ad2cd 100644 --- a/src/config.h +++ b/src/config.h @@ -85,7 +85,9 @@ enum vblank_scheduler_type { /// Internal, private options for debugging and development use. struct debug_options { - + /// Try to reduce frame latency by using vblank interval and render time + /// estimates. Right now it's not working well across drivers. + int smart_frame_pacing; }; /// Structure representing all options. diff --git a/src/picom.c b/src/picom.c index 4c75329d20..b7bb14c6a3 100644 --- a/src/picom.c +++ b/src/picom.c @@ -152,6 +152,12 @@ collect_vblank_interval_statistics(struct vblank_event *e, void *ud) { assert(ps->frame_pacing); assert(ps->vblank_scheduler); + if (!ps->o.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 @@ -220,13 +226,15 @@ enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, vo } // The frame has been finished and presented, record its render time. - 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); + if (ps->o.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->last_schedule_delay = 0; ps->backend_busy = false; @@ -319,6 +327,9 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { return; } + // if ps->o.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, &divisor); auto frame_time = render_statistics_get_vblank_time(&ps->render_stats); if (frame_time == 0) { From 3838b45e2c44cd6c976c4b0b5089b777269e9d2b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 23:14:15 +0000 Subject: [PATCH 165/177] core: simplify pacing logic a bit more Also, vblank event callback should call schedule_render to queue renders instead of starting the draw timer directly, so that the CPU time calculation will be correct. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 8 +-- src/picom.c | 147 ++++++++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 68 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index dd3366d66a..5120672bc5 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -194,10 +194,10 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) { auto after_damage_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000; log_trace("Getting damage took %" PRIu64 " us", after_damage_us - after_sync_fence_us); if (ps->next_render > 0) { - log_trace("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld", - labs((long)after_damage_us - (long)ps->next_render), - after_damage_us < ps->next_render ? "early" : "late", - after_damage_us, ps->next_render); + log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld", + labs((long)after_damage_us - (long)ps->next_render), + after_damage_us < ps->next_render ? "early" : "late", + after_damage_us, ps->next_render); ps->last_schedule_delay = 0; if (after_damage_us > ps->next_render) { ps->last_schedule_delay = after_damage_us - ps->next_render; diff --git a/src/picom.c b/src/picom.c index b7bb14c6a3..9664c8251f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -201,56 +201,50 @@ collect_vblank_interval_statistics(struct vblank_event *e, void *ud) { } return VBLANK_CALLBACK_DONE; } -/// vblank callback scheduled by schedule_render. + +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 record the time it took. -enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, void *ud) { +/// 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->backend_busy); assert(ps->render_queued); assert(ps->vblank_scheduler); - collect_vblank_interval_statistics(e, ud); + log_verbose("Rescheduling render at vblank, msc: %" PRIu64, e->msc); - 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; - } + collect_vblank_interval_statistics(e, ud); - // The frame has been finished and presented, record its render time. - if (ps->o.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); + if (ps->backend_busy) { + 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 (ps->o.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; } - ps->last_schedule_delay = 0; - ps->backend_busy = false; - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - auto now_us = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_nsec / 1000; - double delay_s = 0; - if (ps->next_render > now_us) { - delay_s = (double)(ps->next_render - now_us) / 1000000.0; - } - log_verbose("Prepare to start rendering: delay: %f s, next_render: %" PRIu64 - ", now_us: %" PRIu64, - delay_s, ps->next_render, now_us); - assert(!ev_is_active(&ps->draw_timer)); - ev_timer_set(&ps->draw_timer, delay_s, 0); - ev_timer_start(ps->loop, &ps->draw_timer); + schedule_render(ps, false); return VBLANK_CALLBACK_DONE; } @@ -261,7 +255,7 @@ enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, vo /// 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 current vblank will start, as we can't start render +/// at least know when the next vblank will start, as we can't start render /// before the current rendered frame is diplayed 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, @@ -271,14 +265,20 @@ enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, vo /// 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 is a small caveat in step 2. As a vblank event being delivered +/// 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. +/// 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 @@ -306,6 +306,21 @@ enum vblank_callback_action schedule_render_at_vblank(struct vblank_event *e, vo /// 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; @@ -358,37 +373,41 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { } log_verbose("Delay: %.6lf s, last_msc: %" PRIu64 ", render_budget: %d, " - "frame_time: " - "%" PRIu32 ", now_us: %" PRIu64 ", next_msc: %" PRIu64 ", " - "divisor: %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, - deadline, divisor); + ps->next_render, deadline, divisor); schedule: - ps->render_queued = true; // If the backend is not busy, we just need to schedule the render at the - // specified time; otherwise we need to wait for vblank events. - if (!ps->backend_busy) { - assert(!ev_is_active(&ps->draw_timer)); - ev_timer_set(&ps->draw_timer, delay_s, 0); - ev_timer_start(ps->loop, &ps->draw_timer); - } else { - // We should never set backend_busy to true unless frame_pacing is - // enabled. - assert(ps->vblank_scheduler); - if (!vblank_scheduler_schedule(ps->vblank_scheduler, - schedule_render_at_vblank, ps)) { - // TODO(yshui): handle error here - abort(); - } - } + // 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; } - schedule_render(ps, false); + ps->render_queued = true; + if (ps->o.debug_options.smart_frame_pacing && ps->vblank_scheduler) { + // Make we schedule_render call is synced with vblank events. + // See the comment on schedule_render for more details. + if (!vblank_scheduler_schedule(ps->vblank_scheduler, + reschedule_render_at_vblank, ps)) { + // TODO(yshui): handle error here + abort(); + } + } else { + schedule_render(ps, false); + } } /** From 25d16b03527b2a7da9b23848380bb0593e3472d9 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 09:33:17 +0000 Subject: [PATCH 166/177] x: fix x_request_vblank_event present_notify_msc with divisor == 0 has undocumented special meaning, it means async present notify, which means we could receive MSC notifications from the past. Signed-off-by: Yuxuan Shui --- src/x.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 45abe31197..06c8b939d6 100644 --- a/src/x.c +++ b/src/x.c @@ -780,8 +780,7 @@ bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) { } void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc) { - auto cookie = - xcb_present_notify_msc(c->c, window, 0, msc, 0, 0); + auto cookie = xcb_present_notify_msc(c->c, window, 0, msc, 1, 0); set_cant_fail_cookie(c, cookie); } From c42e6cef0a0f5c6999917d6f36fd549610575d33 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 23:17:09 +0000 Subject: [PATCH 167/177] vblank: winding down vblank events instead of stopping immediately I noticed sometimes full frame rate video is rendered at half frame rate sometimes. That is because the damage notify is sent very close to vblank, and since we request vblank events when we get the damage, we miss the vblank event immediately after, despite the damage happening before the vblank. request next ...... next next damage vblank vblank vblank | | | ...... | v v v v ---------------------->>>>>>--------- `request vblank` is triggered by `damage`, but because it's too close to `next vblank`, that vblank is missed despite we requested before it happening, and we only get `next next vblank`. The result is we will drop every other frame. The solution in this commit is that we will keep requesting vblank events, right after we received a vblank event, even when nobody is asking for them. We would do that for a set number of vblanks before stopping (currently 4). Signed-off-by: Yuxuan Shui --- src/vblank.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vblank.c b/src/vblank.c index 74420ace59..da72911125 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -18,11 +18,19 @@ struct vblank_closure { void *user_data; }; +#define VBLANK_WIND_DOWN 4 + struct vblank_scheduler { struct x_connection *c; size_t callback_capacity, callback_count; struct vblank_closure *callbacks; struct ev_loop *loop; + /// Request extra vblank events even when no callbacks are scheduled. + /// This is because when callbacks are scheduled too close to a vblank, + /// we might send PresentNotifyMsc request too late and miss the vblank event. + /// So we request extra vblank events right after the last vblank event + /// to make sure this doesn't happen. + unsigned int wind_down; xcb_window_t target_window; enum vblank_scheduler_type type; bool vblank_event_requested; @@ -178,7 +186,7 @@ static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t vblank_callback, void *user_data) { - if (self->callback_count == 0) { + if (self->callback_count == 0 && self->wind_down == 0) { vblank_scheduler_schedule_internal(self); } if (self->callback_count == self->callback_capacity) { @@ -204,6 +212,11 @@ vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_e // callbacks might be added during callback invocation, so we need to // copy the callback_count. size_t count = self->callback_count, write_head = 0; + if (count == 0) { + self->wind_down--; + } else { + self->wind_down = VBLANK_WIND_DOWN; + } for (size_t i = 0; i < count; i++) { auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data); switch (action) { @@ -222,7 +235,7 @@ vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_e assert(count == self->callback_count && "callbacks should not be added when " "callbacks are being invoked."); self->callback_count = write_head; - if (self->callback_count) { + if (self->callback_count || self->wind_down) { vblank_scheduler_schedule_internal(self); } } From dd83f550e1af2aaf28638ad7b68b3511ff8169b5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 23:21:07 +0000 Subject: [PATCH 168/177] core: always check if render is finished at next vblank Right now we rely on `reschedule_render_at_vblank` being scheduled for the render finish check. Which means we will only know if a frame has finished rendering the next time we queue a render. And we only check at vblank boundary, which means when a render is queued we have to wait for the next vblank, when had we checked earlier, we will be able to start rendering immediately Signed-off-by: Yuxuan Shui --- src/picom.c | 64 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/picom.c b/src/picom.c index 9664c8251f..8b53551d77 100644 --- a/src/picom.c +++ b/src/picom.c @@ -145,6 +145,38 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t return w; } +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 (ps->o.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; @@ -216,32 +248,10 @@ enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e, 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) { - 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 (ps->o.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_AGAIN; } schedule_render(ps, false); @@ -1815,6 +1825,12 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { if (animation) { queue_redraw(ps); } + if (ps->vblank_scheduler) { + // Even if we might not want to render during next vblank, we want to keep + // `backend_busy` up to date, so when the next render comes, we can + // immediately know if we can render. + vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ps); + } } static void draw_callback(EV_P_ ev_timer *w, int revents) { From db6ed75b605090dd708f5c143e210fea9f782c35 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 20:32:22 +0000 Subject: [PATCH 169/177] core: don't always delay schedule_render to vblank It's kind of dumb anyway. If we get damage event right after a vblank event, we would waste the whole vblank. Instead improve the frame scheduling logic to target the right vblank interval. This only affects smart_frame_pacing anyway. Signed-off-by: Yuxuan Shui --- src/picom.c | 40 ++++++++++++++++++++-------------------- src/statistics.c | 13 +------------ src/statistics.h | 7 +------ 3 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/picom.c b/src/picom.c index 8b53551d77..c2e4251a04 100644 --- a/src/picom.c +++ b/src/picom.c @@ -342,29 +342,39 @@ void schedule_render(session_t *ps, bool triggered_by_vblank attr_unused) { ps->next_render = now_us; if (!ps->frame_pacing || !ps->redirected) { - // If not doing frame pacing, schedule a render immediately unless it's - // already scheduled; if not redirected, we schedule immediately to have a - // chance to redirect. We won't have frame or render timing information + // 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. - if (!ev_is_active(&ps->draw_timer)) { - goto schedule; - } - return; + assert(!ev_is_active(&ps->draw_timer)); + goto schedule; } // if ps->o.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, &divisor); + 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; } - auto const deadline = ps->last_msc_instant + (unsigned long)divisor * frame_time; + 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); @@ -407,17 +417,7 @@ void queue_redraw(session_t *ps) { return; } ps->render_queued = true; - if (ps->o.debug_options.smart_frame_pacing && ps->vblank_scheduler) { - // Make we schedule_render call is synced with vblank events. - // See the comment on schedule_render for more details. - if (!vblank_scheduler_schedule(ps->vblank_scheduler, - reschedule_render_at_vblank, ps)) { - // TODO(yshui): handle error here - abort(); - } - } else { - schedule_render(ps, false); - } + schedule_render(ps, false); } /** diff --git a/src/statistics.c b/src/statistics.c index 5e3d92491d..11c7466ef6 100644 --- a/src/statistics.c +++ b/src/statistics.c @@ -55,26 +55,15 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int /// A `divisor` is also returned, indicating the target framerate. The divisor is /// the number of vblanks we should wait between each frame. A divisor of 1 means /// full framerate, 2 means half framerate, etc. -unsigned int -render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor) { +unsigned int render_statistics_get_budget(struct render_statistics *rs) { if (rs->render_times.nelem < rs->render_times.window_size) { // No valid render time estimates yet. Assume maximum budget. - *divisor = 1; return UINT_MAX; } // N-th percentile of render times, see render_statistics_init for N. auto render_time_percentile = rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times); - auto vblank_time_us = render_statistics_get_vblank_time(rs); - if (vblank_time_us == 0) { - // We don't have a good estimate of the vblank time yet, so we - // assume we can finish in one vblank. - *divisor = 1; - } else { - *divisor = - (unsigned int)(render_time_percentile / rs->vblank_time_us.mean + 1); - } return (unsigned int)render_time_percentile; } diff --git a/src/statistics.h b/src/statistics.h index 42d7bc2d91..a111486111 100644 --- a/src/statistics.h +++ b/src/statistics.h @@ -23,12 +23,7 @@ void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us); /// How much time budget we should give to the backend for rendering, in microseconds. -/// -/// A `divisor` is also returned, indicating the target framerate. The divisor is -/// the number of vblanks we should wait between each frame. A divisor of 1 means -/// full framerate, 2 means half framerate, etc. -unsigned int -render_statistics_get_budget(struct render_statistics *rs, unsigned int *divisor); +unsigned int render_statistics_get_budget(struct render_statistics *rs); /// Return the measured vblank interval in microseconds. Returns 0 if not enough /// samples have been collected yet. From db9b808bfbfc0ff4382d9934aeb8a062dbeaa6dd Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 23:21:53 +0000 Subject: [PATCH 170/177] vblank: add GLX_SGI_video_sync based scheduler Present extension based scheduler doesn't work well on NVIDIA drivers. GLX_SGI_video_sync is less accurate, but is rumoured to work. See [kwin's usage](https://invent.kde.org/plasma/kwin/-/blob/master/src/ backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp) Signed-off-by: Yuxuan Shui --- src/meson.build | 2 +- src/picom.c | 3 +- src/vblank.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++- src/vblank.h | 6 +- 4 files changed, 283 insertions(+), 9 deletions(-) diff --git a/src/meson.build b/src/meson.build index 51f96488cd..a2f9c36c73 100644 --- a/src/meson.build +++ b/src/meson.build @@ -59,7 +59,7 @@ endif if get_option('opengl') cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] - deps += [dependency('gl', required: true), dependency('egl', required: true)] + deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)] srcs += [ 'opengl.c' ] endif diff --git a/src/picom.c b/src/picom.c index c2e4251a04..e5ac833640 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1501,7 +1501,8 @@ static bool redirect_start(session_t *ps) { ps->last_schedule_delay = 0; render_statistics_reset(&ps->render_stats); ps->vblank_scheduler = - vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps)); + vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps), + VBLANK_SCHEDULER_PRESENT); if (!ps->vblank_scheduler) { return false; } diff --git a/src/vblank.c b/src/vblank.c index da72911125..ff83a9a76e 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -2,12 +2,26 @@ #include #include +#include #include +#include #include #include +#include "config.h" + +#ifdef CONFIG_OPENGL +// Enable sgi_video_sync_vblank_scheduler +#include +#include +#include +#include +#include +#include + +#include "backend/gl/glx.h" +#endif #include "compiler.h" -#include "config.h" #include "list.h" // for container_of #include "log.h" #include "vblank.h" @@ -48,6 +62,7 @@ struct present_vblank_scheduler { }; struct vblank_scheduler_ops { + size_t size; void (*init)(struct vblank_scheduler *self); void (*deinit)(struct vblank_scheduler *self); void (*schedule)(struct vblank_scheduler *self); @@ -57,6 +72,242 @@ struct vblank_scheduler_ops { static void vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event); +#ifdef CONFIG_OPENGL +struct sgi_video_sync_vblank_scheduler { + struct vblank_scheduler base; + + // Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread. + // ... and all the thread shenanigans that come with it. + _Atomic unsigned int last_msc; + _Atomic uint64_t last_ust; + ev_async notify; + pthread_t sync_thread; + bool running, error; + + /// Protects `running`, `error` and `base.vblank_event_requested` + pthread_mutex_t vblank_requested_mtx; + pthread_cond_t vblank_requested_cnd; +}; + +struct sgi_video_sync_thread_args { + struct sgi_video_sync_vblank_scheduler *self; + int start_status; + pthread_mutex_t start_mtx; + pthread_cond_t start_cnd; +}; + +static bool check_sgi_video_sync_extension(Display *dpy, int screen) { + const char *glx_ext = glXQueryExtensionsString(dpy, screen); + const char *needle = "GLX_SGI_video_sync"; + char *found = strstr(glx_ext, needle); + if (!found) { + return false; + } + if (found != glx_ext && found[-1] != ' ') { + return false; + } + if (found[strlen(needle)] != ' ' && found[strlen(needle)] != '\0') { + return false; + } + + glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress( + (const GLubyte *)"glXWaitVideoSyncSGI"); + if (!glXWaitVideoSyncSGI) { + return false; + } + return true; +} + +static void *sgi_video_sync_thread(void *data) { + auto args = (struct sgi_video_sync_thread_args *)data; + auto self = args->self; + Display *dpy = XOpenDisplay(NULL); + int error_code = 0; + if (!dpy) { + error_code = 1; + goto start_failed; + } + Window root = DefaultRootWindow(dpy), dummy = None; + int screen = DefaultScreen(dpy); + int ncfg = 0; + GLXFBConfig *cfg_ = glXChooseFBConfig( + dpy, screen, + (int[]){GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 0}, + &ncfg); + GLXContext ctx = NULL; + GLXDrawable drawable = None; + + if (!cfg_) { + error_code = 2; + goto start_failed; + } + GLXFBConfig cfg = cfg_[0]; + XFree(cfg_); + + XVisualInfo *vi = glXGetVisualFromFBConfig(dpy, cfg); + if (!vi) { + error_code = 3; + goto start_failed; + } + + Visual *visual = vi->visual; + const int depth = vi->depth; + XFree(vi); + + Colormap colormap = XCreateColormap(dpy, root, visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = colormap; + + dummy = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual, + CWColormap, &attributes); + XFreeColormap(dpy, colormap); + if (dummy == None) { + error_code = 4; + goto start_failed; + } + + drawable = glXCreateWindow(dpy, cfg, dummy, NULL); + if (drawable == None) { + error_code = 5; + goto start_failed; + } + + ctx = glXCreateNewContext(dpy, cfg, GLX_RGBA_TYPE, 0, true); + if (ctx == NULL) { + error_code = 6; + goto start_failed; + } + + if (!glXMakeContextCurrent(dpy, drawable, drawable, ctx)) { + error_code = 7; + goto start_failed; + } + + if (!check_sgi_video_sync_extension(dpy, screen)) { + error_code = 8; + goto start_failed; + } + + pthread_mutex_lock(&args->start_mtx); + args->start_status = 0; + pthread_cond_signal(&args->start_cnd); + pthread_mutex_unlock(&args->start_mtx); + + pthread_mutex_lock(&self->vblank_requested_mtx); + while (self->running) { + if (!self->base.vblank_event_requested) { + pthread_cond_wait(&self->vblank_requested_cnd, + &self->vblank_requested_mtx); + continue; + } + pthread_mutex_unlock(&self->vblank_requested_mtx); + + unsigned int last_msc; + glXWaitVideoSyncSGI(1, 0, &last_msc); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + atomic_store(&self->last_msc, last_msc); + atomic_store(&self->last_ust, + (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000)); + ev_async_send(self->base.loop, &self->notify); + pthread_mutex_lock(&self->vblank_requested_mtx); + } + pthread_mutex_unlock(&self->vblank_requested_mtx); + goto cleanup; + +start_failed: + pthread_mutex_lock(&args->start_mtx); + args->start_status = error_code; + pthread_cond_signal(&args->start_cnd); + pthread_mutex_unlock(&args->start_mtx); + +cleanup: + if (dpy) { + glXMakeCurrent(dpy, None, NULL); + if (ctx) { + glXDestroyContext(dpy, ctx); + } + if (drawable) { + glXDestroyWindow(dpy, drawable); + } + if (dummy) { + XDestroyWindow(dpy, dummy); + } + XCloseDisplay(dpy); + } + return NULL; +} + +static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) { + auto self = (struct sgi_video_sync_vblank_scheduler *)base; + log_verbose("Requesting vblank event for msc %d", self->last_msc + 1); + pthread_mutex_lock(&self->vblank_requested_mtx); + assert(!base->vblank_event_requested); + base->vblank_event_requested = true; + pthread_cond_signal(&self->vblank_requested_cnd); + pthread_mutex_unlock(&self->vblank_requested_mtx); +} + +static void +sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) { + auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify); + auto event = (struct vblank_event){ + .msc = atomic_load(&sched->last_msc), + .ust = atomic_load(&sched->last_ust), + }; + sched->base.vblank_event_requested = false; + log_verbose("Received vblank event for msc %lu", event.msc); + vblank_scheduler_invoke_callbacks(&sched->base, &event); +} + +static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) { + auto self = (struct sgi_video_sync_vblank_scheduler *)base; + auto args = (struct sgi_video_sync_thread_args){ + .self = self, + .start_status = -1, + }; + pthread_mutex_init(&args.start_mtx, NULL); + pthread_cond_init(&args.start_cnd, NULL); + + base->type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC; + ev_async_init(&self->notify, sgi_video_sync_scheduler_callback); + ev_async_start(base->loop, &self->notify); + pthread_mutex_init(&self->vblank_requested_mtx, NULL); + pthread_cond_init(&self->vblank_requested_cnd, NULL); + + self->running = true; + pthread_create(&self->sync_thread, NULL, sgi_video_sync_thread, &args); + + pthread_mutex_lock(&args.start_mtx); + while (args.start_status == -1) { + pthread_cond_wait(&args.start_cnd, &args.start_mtx); + } + if (args.start_status != 0) { + log_fatal("Failed to start sgi_video_sync_thread, error code: %d", + args.start_status); + abort(); + } + pthread_mutex_destroy(&args.start_mtx); + pthread_cond_destroy(&args.start_cnd); + log_info("Started sgi_video_sync_thread"); +} + +static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) { + auto self = (struct sgi_video_sync_vblank_scheduler *)base; + ev_async_stop(base->loop, &self->notify); + pthread_mutex_lock(&self->vblank_requested_mtx); + self->running = false; + pthread_cond_signal(&self->vblank_requested_cnd); + pthread_mutex_unlock(&self->vblank_requested_mtx); + + pthread_join(self->sync_thread, NULL); + + pthread_mutex_destroy(&self->vblank_requested_mtx); + pthread_cond_destroy(&self->vblank_requested_cnd); +} +#endif + static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) { auto self = (struct present_vblank_scheduler *)base; log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64, @@ -170,11 +421,22 @@ static bool handle_present_events(struct vblank_scheduler *base) { static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = { [VBLANK_SCHEDULER_PRESENT] = { + .size = sizeof(struct present_vblank_scheduler), .init = present_vblank_scheduler_init, .deinit = present_vblank_scheduler_deinit, .schedule = present_vblank_scheduler_schedule, .handle_x_events = handle_present_events, }, +#ifdef CONFIG_OPENGL + [VBLANK_SCHEDULER_SGI_VIDEO_SYNC] = + { + .size = sizeof(struct sgi_video_sync_vblank_scheduler), + .init = sgi_video_sync_scheduler_init, + .deinit = sgi_video_sync_scheduler_deinit, + .schedule = sgi_video_sync_scheduler_schedule, + .handle_x_events = NULL, + }, +#endif }; static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { @@ -250,13 +512,22 @@ void vblank_scheduler_free(struct vblank_scheduler *self) { free(self); } -struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, - xcb_window_t target_window) { - struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler)); +struct vblank_scheduler * +vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, + xcb_window_t target_window, enum vblank_scheduler_type type) { + size_t object_size = vblank_scheduler_ops[type].size; + auto init_fn = vblank_scheduler_ops[type].init; + if (!object_size || !init_fn) { + log_error("Unsupported or invalid vblank scheduler type: %d", type); + return NULL; + } + + assert(object_size >= sizeof(struct vblank_scheduler)); + struct vblank_scheduler *self = calloc(1, object_size); self->target_window = target_window; self->c = c; self->loop = loop; - vblank_scheduler_ops[VBLANK_SCHEDULER_PRESENT].init(self); + init_fn(self); return self; } diff --git a/src/vblank.h b/src/vblank.h index a5073456c2..8050f4878d 100644 --- a/src/vblank.h +++ b/src/vblank.h @@ -8,6 +8,7 @@ #include #include +#include "config.h" #include "x.h" /// An object that schedule vblank events. @@ -38,8 +39,9 @@ typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *ev /// is not enough memory. bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb, void *user_data); -struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, - xcb_window_t target_window); +struct vblank_scheduler * +vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, + xcb_window_t target_window, enum vblank_scheduler_type type); void vblank_scheduler_free(struct vblank_scheduler *); bool vblank_handle_x_events(struct vblank_scheduler *self); From b582d2989e4901606421f633277884e39ab3fc7b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 10:29:31 +0000 Subject: [PATCH 171/177] core: add debug options to override the vblank scheduler Useful for debugging. Signed-off-by: Yuxuan Shui --- src/config.c | 16 ++++++++++++---- src/config.h | 4 ++++ src/picom.c | 10 +++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/config.c b/src/config.c index 8c1ca91d94..7dc28146c5 100644 --- a/src/config.c +++ b/src/config.c @@ -623,11 +623,17 @@ struct debug_options_entry { size_t offset; }; +// clang-format off +const char *vblank_scheduler_str[] = { + [VBLANK_SCHEDULER_PRESENT] = "present", + [VBLANK_SCHEDULER_SGI_VIDEO_SYNC] = "sgi_video_sync", + [LAST_VBLANK_SCHEDULER] = NULL +}; static const struct debug_options_entry debug_options_entries[] = { - "smart_frame_pacing", - NULL, - offsetof(struct debug_options, smart_frame_pacing), + {"smart_frame_pacing", NULL, offsetof(struct debug_options, smart_frame_pacing)}, + {"force_vblank_sched", vblank_scheduler_str, offsetof(struct debug_options, force_vblank_scheduler)}, }; +// clang-format on void parse_debug_option_single(char *setting, struct debug_options *debug_options) { char *equal = strchr(setting, '='); @@ -670,7 +676,9 @@ void parse_debug_option_single(char *setting, struct debug_options *debug_option /// Parse debug options from environment variable `PICOM_DEBUG`. void parse_debug_options(struct debug_options *debug_options) { const char *debug = getenv("PICOM_DEBUG"); - const struct debug_options default_debug_options = {}; + const struct debug_options default_debug_options = { + .force_vblank_scheduler = LAST_VBLANK_SCHEDULER, + }; *debug_options = default_debug_options; if (!debug) { diff --git a/src/config.h b/src/config.h index ac3b6ad2cd..d91dbb4563 100644 --- a/src/config.h +++ b/src/config.h @@ -83,11 +83,15 @@ enum vblank_scheduler_type { LAST_VBLANK_SCHEDULER, }; +extern const char *vblank_scheduler_str[]; + /// Internal, private options for debugging and development use. struct debug_options { /// Try to reduce frame latency by using vblank interval and render time /// estimates. Right now it's not working well across drivers. int smart_frame_pacing; + /// Override the vblank scheduler chosen by the compositor. + int force_vblank_scheduler; }; /// Structure representing all options. diff --git a/src/picom.c b/src/picom.c index e5ac833640..cc1bc42634 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1500,9 +1500,13 @@ static bool redirect_start(session_t *ps) { ps->last_msc = 0; ps->last_schedule_delay = 0; render_statistics_reset(&ps->render_stats); - ps->vblank_scheduler = - vblank_scheduler_new(ps->loop, &ps->c, session_get_target_window(ps), - VBLANK_SCHEDULER_PRESENT); + enum vblank_scheduler_type scheduler_type = VBLANK_SCHEDULER_PRESENT; + if (ps->o.debug_options.force_vblank_scheduler != LAST_VBLANK_SCHEDULER) { + scheduler_type = + (enum vblank_scheduler_type)ps->o.debug_options.force_vblank_scheduler; + } + ps->vblank_scheduler = vblank_scheduler_new( + ps->loop, &ps->c, session_get_target_window(ps), scheduler_type); if (!ps->vblank_scheduler) { return false; } From a28e221b838daea112a55118d082d7097459f154 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 10:29:59 +0000 Subject: [PATCH 172/177] driver: choose sgi_video_sync scheduler for NVIDIA Signed-off-by: Yuxuan Shui --- src/backend/driver.c | 7 +++++++ src/backend/driver.h | 3 +++ src/picom.c | 12 +++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/backend/driver.c b/src/backend/driver.c index b909a62272..f17743a4aa 100644 --- a/src/backend/driver.c +++ b/src/backend/driver.c @@ -19,6 +19,13 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) { } } +enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) { + if (driver & DRIVER_NVIDIA) { + return VBLANK_SCHEDULER_SGI_VIDEO_SYNC; + } + return VBLANK_SCHEDULER_PRESENT; +} + enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { enum driver ret = 0; // First we try doing backend agnostic detection using RANDR diff --git a/src/backend/driver.h b/src/backend/driver.h index e4cc3981f1..1b0877c14b 100644 --- a/src/backend/driver.h +++ b/src/backend/driver.h @@ -7,6 +7,7 @@ #include #include +#include "config.h" #include "utils.h" struct session; @@ -41,6 +42,8 @@ enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_ /// Apply driver specified global workarounds. It's safe to call this multiple times. void apply_driver_workarounds(struct session *ps, enum driver); +/// Choose a vblank scheduler based on the driver. +enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver); // Print driver names to stdout, for diagnostics static inline void print_drivers(enum driver drivers) { diff --git a/src/picom.c b/src/picom.c index cc1bc42634..700320c410 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1493,6 +1493,10 @@ static bool redirect_start(session_t *ps) { ps->frame_pacing = false; } + // Re-detect driver since we now have a backend + ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root); + apply_driver_workarounds(ps, ps->drivers); + if (ps->present_exists && ps->frame_pacing) { // Initialize rendering and frame timing statistics, and frame pacing // states. @@ -1500,11 +1504,13 @@ static bool redirect_start(session_t *ps) { ps->last_msc = 0; ps->last_schedule_delay = 0; render_statistics_reset(&ps->render_stats); - enum vblank_scheduler_type scheduler_type = VBLANK_SCHEDULER_PRESENT; + enum vblank_scheduler_type scheduler_type = + choose_vblank_scheduler(ps->drivers); if (ps->o.debug_options.force_vblank_scheduler != LAST_VBLANK_SCHEDULER) { scheduler_type = (enum vblank_scheduler_type)ps->o.debug_options.force_vblank_scheduler; } + log_info("Using vblank scheduler: %s.", vblank_scheduler_str[scheduler_type]); ps->vblank_scheduler = vblank_scheduler_new( ps->loop, &ps->c, session_get_target_window(ps), scheduler_type); if (!ps->vblank_scheduler) { @@ -1523,10 +1529,6 @@ static bool redirect_start(session_t *ps) { ps->redirected = true; ps->first_frame = true; - // Re-detect driver since we now have a backend - ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root); - apply_driver_workarounds(ps, ps->drivers); - root_damaged(ps); // Repaint the whole screen From 6e0bad0034bc07d868f57003f6c567cc307f4ba2 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 09:53:57 +0000 Subject: [PATCH 173/177] core: make missing dpms extension non-fatal We are not using it for anything at the moment, and it is breaking CI. Signed-off-by: Yuxuan Shui --- src/picom.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/picom.c b/src/picom.c index 700320c410..4bce4e476d 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2139,8 +2139,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ext_info = xcb_get_extension_data(ps->c.c, &xcb_dpms_id); ps->dpms_exists = ext_info && ext_info->present; if (!ps->dpms_exists) { - log_fatal("No DPMS extension"); - exit(1); + log_warn("No DPMS extension"); } // Parse configuration file From 359d004b992a7eff6a85afa93713747fade20aff Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 19 Dec 2023 11:07:55 +0000 Subject: [PATCH 174/177] core: disable frame pacing when vsync is disabled Signed-off-by: Yuxuan Shui --- src/picom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/picom.c b/src/picom.c index 4bce4e476d..5435cecd67 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1484,7 +1484,7 @@ static bool redirect_start(session_t *ps) { pixman_region32_init(&ps->damage_ring[i]); } - ps->frame_pacing = !ps->o.no_frame_pacing; + ps->frame_pacing = !ps->o.no_frame_pacing && ps->o.vsync; if ((ps->o.legacy_backends || ps->o.benchmark || !ps->backend_data->ops->last_render_time) && ps->frame_pacing) { // Disable frame pacing if we are using a legacy backend or if we are in From cacb45fbcdf74d72288b252fc557480906117d24 Mon Sep 17 00:00:00 2001 From: Maxim Solovyov Date: Fri, 22 Dec 2023 01:07:07 +0300 Subject: [PATCH 175/177] core: fix segfaults when the HOME environment variable is not set --- src/config.c | 12 +++++++----- src/config_libconfig.c | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/config.c b/src/config.c index ec39aa4390..f5764c5c39 100644 --- a/src/config.c +++ b/src/config.c @@ -596,15 +596,17 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc // Fall back to searching in user config directory scoped_charp picom_scope = mstrjoin("/picom/", scope); scoped_charp config_home = (char *)xdg_config_home(); - char *ret = locate_auxiliary_file_at(config_home, picom_scope, path); - if (ret) { - return ret; + if (config_home) { + char *ret = locate_auxiliary_file_at(config_home, picom_scope, path); + if (ret) { + return ret; + } } // Fall back to searching in system config directory auto config_dirs = xdg_config_dirs(); for (int i = 0; config_dirs[i]; i++) { - ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path); + char *ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path); if (ret) { free(config_dirs); return ret; @@ -612,7 +614,7 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc } free(config_dirs); - return ret; + return NULL; } /** diff --git a/src/config_libconfig.c b/src/config_libconfig.c index f2a8e07faa..ed2097eb76 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -81,17 +81,19 @@ FILE *open_config_file(const char *cpath, char **ppath) { // First search for config file in user config directory auto config_home = xdg_config_home(); - auto ret = open_config_file_at(config_home, ppath); - free((void *)config_home); - if (ret) { - return ret; + if (config_home) { + auto ret = open_config_file_at(config_home, ppath); + free((void *)config_home); + if (ret) { + return ret; + } } // Fall back to legacy config file in user home directory const char *home = getenv("HOME"); if (home && strlen(home)) { auto path = mstrjoin(home, config_filename_legacy); - ret = fopen(path, "r"); + auto ret = fopen(path, "r"); if (ret && ppath) { *ppath = path; } else { @@ -105,7 +107,7 @@ FILE *open_config_file(const char *cpath, char **ppath) { // Fall back to config file in system config directory auto config_dirs = xdg_config_dirs(); for (int i = 0; config_dirs[i]; i++) { - ret = open_config_file_at(config_dirs[i], ppath); + auto ret = open_config_file_at(config_dirs[i], ppath); if (ret) { free(config_dirs); return ret; From d8f303761bbefa6209792bdd8a286c5eda0b1ef6 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 14 Jan 2024 15:41:10 +0000 Subject: [PATCH 176/177] core: reset msc counter if it went backwards Otherwise we might be repeatedly hitting this condition and spam the warning. Signed-off-by: Yuxuan Shui --- src/picom.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/picom.c b/src/picom.c index 5435cecd67..044a9eb1ea 100644 --- a/src/picom.c +++ b/src/picom.c @@ -222,6 +222,8 @@ collect_vblank_interval_statistics(struct vblank_event *e, void *ud) { 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); From 3390494bfe768ca08463126a865d11a6607c2958 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sun, 14 Jan 2024 15:53:31 +0000 Subject: [PATCH 177/177] Bump version number Signed-off-by: Yuxuan Shui --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 11da327cb0..528a53a029 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('picom', 'c', version: '10', +project('picom', 'c', version: '11', default_options: ['c_std=c11', 'warning_level=1']) cc = meson.get_compiler('c')