Merge branch 'yshui:next' into next
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# Build files
|
||||
.deps
|
||||
.direnv
|
||||
aclocal.m4
|
||||
autom4te.cache
|
||||
config.log
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Unreleased
|
||||
|
||||
## New features
|
||||
|
||||
* Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168)
|
||||
|
||||
# v11.1 (2024-Jan-28)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix missing fading on window close for some window managers. (#704)
|
||||
|
||||
@@ -55,7 +55,7 @@ libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixma
|
||||
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 pcre2-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 xcb-util-devel
|
||||
```
|
||||
|
||||
To build the documents, you need `asciidoc`
|
||||
|
||||
@@ -209,7 +209,7 @@ struct backend_operations {
|
||||
* @param backend_data backend data
|
||||
* @param pixmap X pixmap to bind
|
||||
* @param fmt information of the pixmap's visual
|
||||
* @param owned whether the ownership of the pixmap is transfered to the backend
|
||||
* @param owned whether the ownership of the pixmap is transferred to the backend
|
||||
* @return backend internal data structure bound with this pixmap
|
||||
*/
|
||||
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
|
||||
@@ -225,7 +225,7 @@ struct backend_operations {
|
||||
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
|
||||
/// size of `width + radius * 2` x `height + radius * 2`. Radius is set when the
|
||||
/// shadow context is created.
|
||||
/// Default implementation: default_render_shadow
|
||||
///
|
||||
@@ -284,9 +284,9 @@ struct backend_operations {
|
||||
bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
|
||||
attr_nonnull(1, 2);
|
||||
|
||||
/// Get the age of the buffer content we are currently rendering ontop
|
||||
/// Get the age of the buffer content we are currently rendering on top
|
||||
/// of. The buffer that has just been `present`ed has a buffer age of 1.
|
||||
/// Everytime `present` is called, buffers get older. Return -1 if the
|
||||
/// Every time `present` is called, buffers get older. Return -1 if the
|
||||
/// buffer is empty.
|
||||
///
|
||||
/// Optional
|
||||
@@ -295,7 +295,7 @@ struct backend_operations {
|
||||
/// 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.
|
||||
/// reporting the time of the just presented frame.
|
||||
///
|
||||
/// Optional, if not available, the most conservative estimation will be used.
|
||||
bool (*last_render_time)(backend_t *backend_data, struct timespec *ts);
|
||||
|
||||
@@ -19,11 +19,14 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) {
|
||||
}
|
||||
}
|
||||
|
||||
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) {
|
||||
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) {
|
||||
enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT;
|
||||
#ifdef CONFIG_OPENGL
|
||||
if (driver & DRIVER_NVIDIA) {
|
||||
return VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||
type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||
}
|
||||
return VBLANK_SCHEDULER_PRESENT;
|
||||
#endif
|
||||
return type;
|
||||
}
|
||||
|
||||
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
|
||||
|
||||
@@ -185,7 +185,7 @@ void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader)
|
||||
* @note In order to reduce number of textures which needs to be
|
||||
* allocated and deleted during this recursive render
|
||||
* we reuse the same two textures for render source and
|
||||
* destination simply by alterating between them.
|
||||
* destination simply by alternating between them.
|
||||
* Unfortunately on first iteration source_texture might
|
||||
* be read-only. In this case we will select auxiliary_texture as
|
||||
* destination_texture in order not to touch that read-only source
|
||||
@@ -253,7 +253,7 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina
|
||||
|
||||
/*
|
||||
* @brief Builds a 1x1 texture which has color corresponding to the average of all
|
||||
* pixels of img by recursively rendering into texture of quorter the size (half
|
||||
* pixels of img by recursively rendering into texture of quarter the size (half
|
||||
* width and half height).
|
||||
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
|
||||
* deleted when the gl_image is released.
|
||||
|
||||
@@ -78,7 +78,7 @@ typedef struct {
|
||||
GLint color_loc;
|
||||
} gl_fill_shader_t;
|
||||
|
||||
/// @brief Wrapper of a binded GL texture.
|
||||
/// @brief Wrapper of a bound GL texture.
|
||||
struct gl_texture {
|
||||
int refcount;
|
||||
bool has_alpha;
|
||||
|
||||
@@ -244,8 +244,8 @@ typedef struct session {
|
||||
/// 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
|
||||
/// will have to wait for either the current render to finish, or the current
|
||||
/// back buffer to 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
|
||||
@@ -279,7 +279,7 @@ typedef struct session {
|
||||
struct x_convolution_kernel **blur_kerns_cache;
|
||||
/// If we should quit
|
||||
bool quit : 1;
|
||||
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
|
||||
// TODO(yshui) use separate flags for different kinds of updates so we don't
|
||||
// waste our time.
|
||||
/// Whether there are pending updates, like window creation, etc.
|
||||
bool pending_updates : 1;
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
#else
|
||||
# define attr_warn_unused_result
|
||||
#endif
|
||||
// An alias for conveninence
|
||||
// An alias for convenience
|
||||
#define must_use attr_warn_unused_result
|
||||
|
||||
#if __has_attribute(nonnull)
|
||||
|
||||
@@ -563,7 +563,7 @@ static char *locate_auxiliary_file_at(const char *base, const char *scope, const
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
|
||||
* Get a path of an auxiliary file to read, could be a shader file, or any supplementary
|
||||
* file.
|
||||
*
|
||||
* Follows the XDG specification to search for the shader file in configuration locations.
|
||||
|
||||
@@ -366,7 +366,7 @@ char **xdg_config_dirs(void);
|
||||
/// Parse a configuration file
|
||||
/// Returns the actually config_file name used, allocated on heap
|
||||
/// Outputs:
|
||||
/// shadow_enable = whether shaodw is enabled globally
|
||||
/// shadow_enable = whether shadow is enabled globally
|
||||
/// fading_enable = whether fading is enabled globally
|
||||
/// win_option_mask = whether option overrides for specific window type is set for given
|
||||
/// options
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
/// made the query when those events were already in the queue. so the reply you got is
|
||||
/// more up-to-date than the events). Also, handling events when other client are making
|
||||
/// concurrent requests is not good. Because the server states are changing without you
|
||||
/// knowning them. This is super racy, and can cause lots of potential problems.
|
||||
/// knowing them. This is super racy, and can cause lots of potential problems.
|
||||
///
|
||||
/// All of above mandates we do these things:
|
||||
/// 1. Grab server when handling events
|
||||
@@ -324,7 +324,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
|
||||
}
|
||||
|
||||
if (ev->parent == ps->c.screen_info->root) {
|
||||
// X will generate reparent notifiy even if the parent didn't actually
|
||||
// X will generate reparent notify even if the parent didn't actually
|
||||
// change (i.e. reparent again to current parent). So we check if that's
|
||||
// the case
|
||||
auto w = find_win(ps, ev->window);
|
||||
@@ -465,7 +465,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
||||
}
|
||||
}
|
||||
|
||||
// Unconcerned about any other proprties on root window
|
||||
// Unconcerned about any other properties on root window
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
||||
}
|
||||
|
||||
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
|
||||
// Unnecessay until we remove the queue_redraw in ev_handle
|
||||
// Unnecessary until we remove the queue_redraw in ev_handle
|
||||
queue_redraw(ps);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *
|
||||
fflags |= NOTE_CLOSE_WRITE;
|
||||
#else
|
||||
// NOTE_WRITE will receive notification more frequent than necessary, so is less
|
||||
// preferrable
|
||||
// preferable
|
||||
fflags |= NOTE_WRITE;
|
||||
#endif
|
||||
struct kevent ev = {
|
||||
|
||||
@@ -96,7 +96,7 @@ static inline double estimate_first_row_sum(double size, double r) {
|
||||
// `a` is gaussian at (size, 0)
|
||||
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
|
||||
// The sum of the whole kernel is normalized to 1, i.e. each element is divided by
|
||||
// factor sqaured. So the sum of the first row is a * factor / factor^2 = a /
|
||||
// factor squared. So the sum of the first row is a * factor / factor^2 = a /
|
||||
// factor
|
||||
return a / factor;
|
||||
}
|
||||
|
||||
@@ -1129,7 +1129,7 @@ glx_blur_dst_end:
|
||||
// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border
|
||||
// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in
|
||||
// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the
|
||||
// documentation for the xcb_xrender extension is literaly non existent...
|
||||
// documentation for the xcb_xrender extension is literally non existent...
|
||||
//
|
||||
// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
|
||||
// the WM's perspective there are multiple ways to implement window borders. Using
|
||||
|
||||
@@ -75,7 +75,7 @@ typedef struct glx_session {
|
||||
glx_round_pass_t *round_passes;
|
||||
} glx_session_t;
|
||||
|
||||
/// @brief Wrapper of a binded GLX texture.
|
||||
/// @brief Wrapper of a bound GLX texture.
|
||||
typedef struct _glx_texture {
|
||||
GLuint texture;
|
||||
GLXPixmap glpixmap;
|
||||
@@ -121,9 +121,9 @@ bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int wi
|
||||
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
|
||||
|
||||
/**
|
||||
* Check if a texture is binded, or is binded to the given pixmap.
|
||||
* Check if a texture is bound, or is bound to the given pixmap.
|
||||
*/
|
||||
static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
|
||||
static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
|
||||
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
|
||||
}
|
||||
|
||||
|
||||
@@ -325,9 +325,9 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all
|
||||
|
||||
int o = 0, longopt_idx = -1;
|
||||
|
||||
// Pre-parse the commandline arguments to check for --config and invalid
|
||||
// Pre-parse the command line arguments to check for --config and invalid
|
||||
// switches
|
||||
// Must reset optind to 0 here in case we reread the commandline
|
||||
// Must reset optind to 0 here in case we reread the command line
|
||||
// arguments
|
||||
optind = 1;
|
||||
*config_file = NULL;
|
||||
@@ -379,7 +379,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
||||
// instead of commas in atof().
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
// Parse commandline arguments. Range checking will be done later.
|
||||
// Parse command line arguments. Range checking will be done later.
|
||||
|
||||
bool failed = false;
|
||||
const char *deprecation_message attr_unused =
|
||||
@@ -731,7 +731,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
||||
opt->blur_strength = atoi(optarg);
|
||||
break;
|
||||
case 333:
|
||||
// --cornor-radius
|
||||
// --corner-radius
|
||||
opt->corner_radius = atoi(optarg);
|
||||
break;
|
||||
case 334:
|
||||
|
||||
45
src/picom.c
45
src/picom.c
@@ -270,7 +270,7 @@ enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e,
|
||||
/// is no render currently scheduled. i.e. render_queued == false.
|
||||
/// 2. then, we need to figure out the best time to start rendering. we need to
|
||||
/// at least know when the next vblank will start, as we can't start render
|
||||
/// before the current rendered frame is diplayed on screen. we have this
|
||||
/// before the current rendered frame is displayed on screen. we have this
|
||||
/// information from the vblank scheduler, it will notify us when that happens.
|
||||
/// we might also want to delay the rendering even further to reduce latency,
|
||||
/// this is discussed below, in FUTURE WORKS.
|
||||
@@ -1330,14 +1330,43 @@ void root_damaged(session_t *ps) {
|
||||
}
|
||||
auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
|
||||
if (pixmap != XCB_NONE) {
|
||||
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(
|
||||
ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
|
||||
if (!r) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
// We used to assume that pixmaps pointed by the root background
|
||||
// pixmap atoms are owned by the root window and have the same
|
||||
// depth and hence the same visual that we can use to bind them.
|
||||
// However, some applications break this assumption, e.g. the
|
||||
// Xfce's desktop manager xfdesktop that sets the _XROOTPMAP_ID
|
||||
// atom to a pixmap owned by it that seems to always have 32 bpp
|
||||
// depth when the common root window's depth is 24 bpp. So use the
|
||||
// root window's visual only if the root background pixmap's depth
|
||||
// matches the root window's depth. Otherwise, find a suitable
|
||||
// visual for the root background pixmap's depth and use it.
|
||||
//
|
||||
// We can't obtain a suitable visual for the root background
|
||||
// pixmap the same way as the win_bind_pixmap function because it
|
||||
// requires a window and we have only a pixmap. We also can't not
|
||||
// bind the root background pixmap in case of depth mismatch
|
||||
// because some options rely on it's content, e.g.
|
||||
// transparent-clipping.
|
||||
xcb_visualid_t visual =
|
||||
r->depth == ps->c.screen_info->root_depth
|
||||
? ps->c.screen_info->root_visual
|
||||
: x_get_visual_for_depth(&ps->c, r->depth);
|
||||
free(r);
|
||||
|
||||
ps->root_image = ps->backend_data->ops->bind_pixmap(
|
||||
ps->backend_data, pixmap,
|
||||
x_get_visual_info(&ps->c, ps->c.screen_info->root_visual), false);
|
||||
ps->backend_data, pixmap, x_get_visual_info(&ps->c, visual), false);
|
||||
if (ps->root_image) {
|
||||
ps->backend_data->ops->set_image_property(
|
||||
ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
|
||||
ps->root_image, (int[]){ps->root_width, ps->root_height});
|
||||
} else {
|
||||
err:
|
||||
log_error("Failed to bind root back pixmap");
|
||||
}
|
||||
}
|
||||
@@ -2040,7 +2069,7 @@ static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused
|
||||
/**
|
||||
* Turn on the program reset flag.
|
||||
*
|
||||
* This will result in the compostior resetting itself after next paint.
|
||||
* This will result in the compositor resetting itself after next paint.
|
||||
*/
|
||||
static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) {
|
||||
log_info("picom is resetting...");
|
||||
@@ -2117,11 +2146,11 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data)
|
||||
/**
|
||||
* Initialize a session.
|
||||
*
|
||||
* @param argc number of commandline arguments
|
||||
* @param argv commandline arguments
|
||||
* @param argc number of command line arguments
|
||||
* @param argv command line arguments
|
||||
* @param dpy the X Display
|
||||
* @param config_file the path to the config file
|
||||
* @param all_xerros whether we should report all X errors
|
||||
* @param all_xerrors whether we should report all X errors
|
||||
* @param fork whether we will fork after initialization
|
||||
*/
|
||||
static session_t *session_init(int argc, char **argv, Display *dpy,
|
||||
@@ -2673,7 +2702,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
|
||||
ps->server_grabbed = true;
|
||||
|
||||
// We are going to pull latest information from X server now, events sent by X
|
||||
// earlier is irrelavant at this point.
|
||||
// earlier is irrelevant 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);
|
||||
|
||||
24
src/render.c
24
src/render.c
@@ -89,7 +89,7 @@ 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_bound(ppaint->ptex, ppaint->pixmap)) {
|
||||
return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei,
|
||||
repeat, fbcfg);
|
||||
}
|
||||
@@ -378,7 +378,7 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) {
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OPENGL
|
||||
if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) {
|
||||
if (BKEND_GLX == ps->o.backend && !glx_tex_bound(ppaint->ptex, XCB_NONE)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
@@ -604,20 +604,27 @@ static bool get_root_tile(session_t *ps) {
|
||||
bool fill = false;
|
||||
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)) {
|
||||
pixmap = XCB_NONE;
|
||||
xcb_get_geometry_reply_t *r;
|
||||
if (pixmap) {
|
||||
r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
|
||||
}
|
||||
|
||||
// Create a pixmap if there isn't any
|
||||
if (!pixmap) {
|
||||
xcb_visualid_t visual;
|
||||
if (!pixmap || !r) {
|
||||
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;
|
||||
}
|
||||
visual = ps->c.screen_info->root_visual;
|
||||
fill = true;
|
||||
} else {
|
||||
visual = r->depth == ps->c.screen_info->root_depth
|
||||
? ps->c.screen_info->root_visual
|
||||
: x_get_visual_for_depth(&ps->c, r->depth);
|
||||
free(r);
|
||||
}
|
||||
|
||||
// Create Picture
|
||||
@@ -625,7 +632,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->c.screen_info->root_visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
|
||||
&ps->c, visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
|
||||
|
||||
// Fill pixmap if needed
|
||||
if (fill) {
|
||||
@@ -646,8 +653,7 @@ static bool get_root_tile(session_t *ps) {
|
||||
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->c.screen_info->root_visual, false);
|
||||
return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, visual, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -51,10 +51,6 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
if (rs->render_times.nelem < rs->render_times.window_size) {
|
||||
// No valid render time estimates yet. Assume maximum budget.
|
||||
|
||||
@@ -141,10 +141,10 @@ void rolling_max_pop_front(struct rolling_max *rm, int front) {
|
||||
}
|
||||
|
||||
void rolling_max_push_back(struct rolling_max *rm, int val) {
|
||||
// Update the prority queue.
|
||||
// Update the priority 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
|
||||
// come before 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);
|
||||
|
||||
@@ -236,7 +236,7 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
|
||||
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
|
||||
})
|
||||
|
||||
/// @brief Wrapper of ealloc().
|
||||
/// @brief Wrapper of realloc().
|
||||
#define crealloc(ptr, nmemb) \
|
||||
({ \
|
||||
auto tmp = (nmemb); \
|
||||
|
||||
91
src/vblank.c
91
src/vblank.c
@@ -18,7 +18,6 @@
|
||||
#include <X11/Xutil.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "backend/gl/glx.h"
|
||||
#endif
|
||||
|
||||
#include "compiler.h"
|
||||
@@ -63,9 +62,9 @@ struct present_vblank_scheduler {
|
||||
|
||||
struct vblank_scheduler_ops {
|
||||
size_t size;
|
||||
void (*init)(struct vblank_scheduler *self);
|
||||
bool (*init)(struct vblank_scheduler *self);
|
||||
void (*deinit)(struct vblank_scheduler *self);
|
||||
void (*schedule)(struct vblank_scheduler *self);
|
||||
bool (*schedule)(struct vblank_scheduler *self);
|
||||
bool (*handle_x_events)(struct vblank_scheduler *self);
|
||||
};
|
||||
|
||||
@@ -78,13 +77,14 @@ struct sgi_video_sync_vblank_scheduler {
|
||||
|
||||
// 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;
|
||||
_Atomic unsigned int current_msc;
|
||||
_Atomic uint64_t current_ust;
|
||||
ev_async notify;
|
||||
pthread_t sync_thread;
|
||||
bool running, error;
|
||||
unsigned int last_msc;
|
||||
|
||||
/// Protects `running`, `error` and `base.vblank_event_requested`
|
||||
/// Protects `running`, and `base.vblank_event_requested`
|
||||
pthread_mutex_t vblank_requested_mtx;
|
||||
pthread_cond_t vblank_requested_cnd;
|
||||
};
|
||||
@@ -96,6 +96,8 @@ struct sgi_video_sync_thread_args {
|
||||
pthread_cond_t start_cnd;
|
||||
};
|
||||
|
||||
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
|
||||
|
||||
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";
|
||||
@@ -207,8 +209,8 @@ static void *sgi_video_sync_thread(void *data) {
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
atomic_store(&self->last_msc, last_msc);
|
||||
atomic_store(&self->last_ust,
|
||||
atomic_store(&self->current_msc, last_msc);
|
||||
atomic_store(&self->current_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);
|
||||
@@ -239,34 +241,30 @@ cleanup:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
|
||||
static bool 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);
|
||||
if (self->error) {
|
||||
return false;
|
||||
}
|
||||
log_verbose("Requesting vblank event for msc %d", self->current_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);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents);
|
||||
|
||||
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
|
||||
static bool 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,
|
||||
};
|
||||
bool succeeded = true;
|
||||
pthread_mutex_init(&args.start_mtx, NULL);
|
||||
pthread_cond_init(&args.start_cnd, NULL);
|
||||
|
||||
@@ -286,11 +284,15 @@ static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
|
||||
if (args.start_status != 0) {
|
||||
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
|
||||
args.start_status);
|
||||
abort();
|
||||
succeeded = false;
|
||||
} else {
|
||||
log_info("Started sgi_video_sync_thread");
|
||||
}
|
||||
self->error = !succeeded;
|
||||
self->last_msc = 0;
|
||||
pthread_mutex_destroy(&args.start_mtx);
|
||||
pthread_cond_destroy(&args.start_cnd);
|
||||
log_info("Started sgi_video_sync_thread");
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
|
||||
@@ -306,15 +308,45 @@ static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
|
||||
pthread_mutex_destroy(&self->vblank_requested_mtx);
|
||||
pthread_cond_destroy(&self->vblank_requested_cnd);
|
||||
}
|
||||
|
||||
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 msc = atomic_load(&sched->current_msc);
|
||||
if (sched->last_msc == msc) {
|
||||
// NVIDIA spams us with duplicate vblank events after a suspend/resume
|
||||
// cycle. Recreating the X connection and GLX context seems to fix this.
|
||||
// Oh NVIDIA.
|
||||
log_warn("Duplicate vblank event found with msc %d. Possible NVIDIA bug?", msc);
|
||||
log_warn("Resetting the vblank scheduler");
|
||||
sgi_video_sync_scheduler_deinit(&sched->base);
|
||||
sched->base.vblank_event_requested = false;
|
||||
if (!sgi_video_sync_scheduler_init(&sched->base)) {
|
||||
log_error("Failed to reset the vblank scheduler");
|
||||
} else {
|
||||
sgi_video_sync_scheduler_schedule(&sched->base);
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto event = (struct vblank_event){
|
||||
.msc = msc,
|
||||
.ust = atomic_load(&sched->current_ust),
|
||||
};
|
||||
sched->base.vblank_event_requested = false;
|
||||
sched->last_msc = msc;
|
||||
log_verbose("Received vblank event for msc %lu", event.msc);
|
||||
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
|
||||
static bool 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;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) {
|
||||
@@ -327,7 +359,7 @@ static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unus
|
||||
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
||||
}
|
||||
|
||||
static void present_vblank_scheduler_init(struct vblank_scheduler *base) {
|
||||
static bool 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);
|
||||
@@ -339,6 +371,7 @@ static void present_vblank_scheduler_init(struct vblank_scheduler *base) {
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
|
||||
@@ -439,17 +472,19 @@ static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDU
|
||||
#endif
|
||||
};
|
||||
|
||||
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
|
||||
static bool 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);
|
||||
return fn(self);
|
||||
}
|
||||
|
||||
bool vblank_scheduler_schedule(struct vblank_scheduler *self,
|
||||
vblank_callback_t vblank_callback, void *user_data) {
|
||||
if (self->callback_count == 0 && self->wind_down == 0) {
|
||||
vblank_scheduler_schedule_internal(self);
|
||||
if (!vblank_scheduler_schedule_internal(self)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (self->callback_count == self->callback_capacity) {
|
||||
size_t new_capacity =
|
||||
|
||||
18
src/win.c
18
src/win.c
@@ -1222,7 +1222,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
|
||||
|
||||
// Delayed update of shadow image
|
||||
// By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to
|
||||
// re-create or release the shaodw in based on whether w->shadow
|
||||
// re-create or release the shadow in based on whether w->shadow
|
||||
// is set.
|
||||
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
|
||||
|
||||
@@ -1407,16 +1407,16 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
|
||||
* Determine if a window should have rounded corners.
|
||||
*/
|
||||
static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
|
||||
if (ps->o.corner_radius == 0) {
|
||||
w->corner_radius = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (ps->o.corner_radius == 0 && !radius_override) {
|
||||
w->corner_radius = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)) ||
|
||||
@@ -2259,7 +2259,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
|
||||
|
||||
// Add border width because we are using a different origin.
|
||||
// X thinks the top left of the inner window is the origin
|
||||
// (for the bounding shape, althought xcb_get_geometry thinks
|
||||
// (for the bounding shape, although xcb_get_geometry thinks
|
||||
// the outer top left (outer means outside of the window
|
||||
// border) is the origin),
|
||||
// We think the top left of the border is the origin
|
||||
@@ -2602,7 +2602,7 @@ bool destroy_win_start(session_t *ps, struct win *w) {
|
||||
HASH_DEL(ps->windows, w);
|
||||
|
||||
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
|
||||
// Window is already unmapped, or is an unmanged window, just
|
||||
// Window is already unmapped, or is an unmanaged window, just
|
||||
// destroy it
|
||||
destroy_win_finish(ps, w);
|
||||
return true;
|
||||
@@ -3031,7 +3031,7 @@ static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a window is fulscreen using EWMH
|
||||
* Check if a window is full-screen using EWMH
|
||||
*
|
||||
* TODO(yshui) cache this property
|
||||
*/
|
||||
|
||||
@@ -278,13 +278,13 @@ struct managed_win {
|
||||
switch_t shadow_force;
|
||||
/// Opacity of the shadow. Affected by window opacity and frame opacity.
|
||||
double shadow_opacity;
|
||||
/// X offset of shadow. Affected by commandline argument.
|
||||
/// X offset of shadow. Affected by command line argument.
|
||||
int shadow_dx;
|
||||
/// Y offset of shadow. Affected by commandline argument.
|
||||
/// Y offset of shadow. Affected by command line argument.
|
||||
int shadow_dy;
|
||||
/// Width of shadow. Affected by window size and commandline argument.
|
||||
/// Width of shadow. Affected by window size and command line argument.
|
||||
int shadow_width;
|
||||
/// Height of shadow. Affected by window size and commandline argument.
|
||||
/// Height of shadow. Affected by window size and command line argument.
|
||||
int shadow_height;
|
||||
/// Picture to render shadow. Affected by window size.
|
||||
paint_t shadow_paint;
|
||||
|
||||
@@ -68,7 +68,7 @@ typedef enum {
|
||||
} winstate_t;
|
||||
|
||||
enum win_flags {
|
||||
// Note: *_NONE flags are mostly redudant and meant for detecting logical errors
|
||||
// Note: *_NONE flags are mostly redundant and meant for detecting logical errors
|
||||
// in the code
|
||||
|
||||
/// pixmap is out of date, will be update in win_process_flags
|
||||
|
||||
36
src/x.c
36
src/x.c
@@ -321,6 +321,21 @@ xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standa
|
||||
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
|
||||
}
|
||||
|
||||
xcb_visualid_t x_get_visual_for_depth(struct x_connection *c, uint8_t depth) {
|
||||
xcb_screen_iterator_t screen_it = xcb_setup_roots_iterator(xcb_get_setup(c->c));
|
||||
for (; screen_it.rem; xcb_screen_next(&screen_it)) {
|
||||
xcb_depth_iterator_t depth_it =
|
||||
xcb_screen_allowed_depths_iterator(screen_it.data);
|
||||
for (; depth_it.rem; xcb_depth_next(&depth_it)) {
|
||||
if (depth_it.data->depth == depth) {
|
||||
return xcb_depth_visuals_iterator(depth_it.data).data->visual_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
xcb_render_pictformat_t
|
||||
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
|
||||
x_get_server_pictfmts(c);
|
||||
@@ -689,27 +704,6 @@ xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, i
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a pixmap.
|
||||
*
|
||||
* Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
|
||||
* are better ways.
|
||||
*/
|
||||
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->c, xcb_get_geometry(c->c, pixmap), NULL);
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = r->width && r->height;
|
||||
free(r);
|
||||
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
|
||||
|
||||
4
src/x.h
4
src/x.h
@@ -356,8 +356,6 @@ const char *x_strerror(xcb_generic_error_t *e);
|
||||
|
||||
xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height);
|
||||
|
||||
bool x_validate_pixmap(struct x_connection *, xcb_pixmap_t pxmap);
|
||||
|
||||
/**
|
||||
* Free a <code>winprop_t</code>.
|
||||
*
|
||||
@@ -408,6 +406,8 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
|
||||
|
||||
xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std);
|
||||
|
||||
xcb_visualid_t x_get_visual_for_depth(struct x_connection *c, uint8_t depth);
|
||||
|
||||
xcb_render_pictformat_t
|
||||
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user