fix: route dma-buf straight to GPU thumbnails without CPU probing

Probing every committed buffer with wlr_buffer_begin_data_ptr_access
first can stall on GPU fences at 60Hz, visibly slowing GPU clients
like alacritty when --overlay-previews is on. Check
wlr_buffer_get_dmabuf (a cheap attribute read with no mapping or
sync) first and lock dma-bufs directly for GPU import; only
non-dma-buf buffers go through the CPU snapshot path.
This commit is contained in:
Andrew Hamilton 2026-09-27 12:54:46 +10:00
parent ca9b5ece2c
commit 8b8137684a

161
kick.c
View file

@ -2706,79 +2706,12 @@ static void thumb_capture(struct kick_window *w) {
struct wlr_surface *s = window_surface(w);
struct wlr_buffer *b = (s && s->current.buffer) ? s->current.buffer : NULL;
if (b && b->width > 0 && b->height > 0) {
void *data = NULL;
uint32_t fmt = 0;
size_t stride = 0;
bool begun = wlr_buffer_begin_data_ptr_access(
b, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &fmt, &stride);
bool cpu = begun &&
(fmt == DRM_FORMAT_ARGB8888 || fmt == DRM_FORMAT_XRGB8888);
if (cpu) {
int bw = b->width, bh = b->height;
int tw = overlay_preview_snap_w(), th = overlay_preview_snap_h();
if (!w->thumb_px || w->thumb_pw != tw || w->thumb_ph != th) {
free(w->thumb_px);
w->thumb_px = malloc((size_t)tw * th * 4);
w->thumb_pw = tw;
w->thumb_ph = th;
}
if (w->thumb_px) {
/* Box-averaged downscale: average every source pixel in
each destination pixel's block instead of sampling one,
so glyph strokes survive as shapes rather than speckle.
Average in premultiplied space. */
for (int y = 0; y < th; y++) {
int sy0 = (int)((int64_t)y * bh / th);
int sy1 = (int)(((int64_t)(y + 1) * bh + th - 1) / th);
if (sy1 <= sy0) sy1 = sy0 + 1;
if (sy1 > bh) sy1 = bh;
for (int x = 0; x < tw; x++) {
int sx0 = (int)((int64_t)x * bw / tw);
int sx1 = (int)(((int64_t)(x + 1) * bw + tw - 1) / tw);
if (sx1 <= sx0) sx1 = sx0 + 1;
if (sx1 > bw) sx1 = bw;
unsigned long sa = 0, sr = 0, sg = 0, sb = 0;
unsigned long n = 0;
for (int sy = sy0; sy < sy1; sy++) {
const uint32_t *row = (const uint32_t *)
((const char *)data + (size_t)sy * stride);
for (int sx = sx0; sx < sx1; sx++) {
uint32_t p = row[sx];
unsigned a = (p >> 24) & 0xff;
if (fmt == DRM_FORMAT_XRGB8888 || a == 0)
a = 0xff;
unsigned r = (p >> 16) & 0xff,
g = (p >> 8) & 0xff,
bl = p & 0xff;
sa += a;
sr += r * a / 255;
sg += g * a / 255;
sb += bl * a / 255;
n++;
}
}
if (n == 0) n = 1;
unsigned a = (unsigned)(sa / n);
unsigned r = (unsigned)(sr / n);
unsigned g = (unsigned)(sg / n);
unsigned bl = (unsigned)(sb / n);
w->thumb_px[(size_t)y * tw + x] =
(a << 24) | (r << 16) | (g << 8) | bl;
}
}
w->thumb_src = THUMB_SRC_SNAP;
w->thumb_dirty = true;
}
wlr_buffer_end_data_ptr_access(b);
if (w->thumb_buf) {
/* Source switched back to SHM: drop the GPU buffer lock. */
wlr_buffer_unlock(w->thumb_buf);
w->thumb_buf = NULL;
}
} else {
/* Not CPU-readable (dma-buf): hold a lock and import at draw. */
if (begun)
wlr_buffer_end_data_ptr_access(b);
/* Route dma-buf straight to the GPU path: probing it with a CPU
data-pointer access first can stall on GPU fences at 60Hz, which
visibly slows GPU clients like alacritty. get_dmabuf is a cheap
attribute read with no mapping or sync involved. */
struct wlr_dmabuf_attributes dma;
if (wlr_buffer_get_dmabuf(b, &dma)) {
if (b != w->thumb_buf) {
if (w->thumb_buf)
wlr_buffer_unlock(w->thumb_buf);
@ -2786,6 +2719,88 @@ static void thumb_capture(struct kick_window *w) {
}
w->thumb_src = THUMB_SRC_DMABUF;
w->thumb_dirty = true;
} else {
void *data = NULL;
uint32_t fmt = 0;
size_t stride = 0;
bool begun = wlr_buffer_begin_data_ptr_access(
b, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &fmt, &stride);
bool cpu = begun &&
(fmt == DRM_FORMAT_ARGB8888 || fmt == DRM_FORMAT_XRGB8888);
if (cpu) {
int bw = b->width, bh = b->height;
int tw = overlay_preview_snap_w(), th = overlay_preview_snap_h();
if (!w->thumb_px || w->thumb_pw != tw || w->thumb_ph != th) {
free(w->thumb_px);
w->thumb_px = malloc((size_t)tw * th * 4);
w->thumb_pw = tw;
w->thumb_ph = th;
}
if (w->thumb_px) {
/* Box-averaged downscale: average every source pixel in
each destination pixel's block instead of sampling one,
so glyph strokes survive as shapes rather than speckle.
Average in premultiplied space. */
for (int y = 0; y < th; y++) {
int sy0 = (int)((int64_t)y * bh / th);
int sy1 = (int)(((int64_t)(y + 1) * bh + th - 1) / th);
if (sy1 <= sy0) sy1 = sy0 + 1;
if (sy1 > bh) sy1 = bh;
for (int x = 0; x < tw; x++) {
int sx0 = (int)((int64_t)x * bw / tw);
int sx1 = (int)(((int64_t)(x + 1) * bw + tw - 1) / tw);
if (sx1 <= sx0) sx1 = sx0 + 1;
if (sx1 > bw) sx1 = bw;
unsigned long sa = 0, sr = 0, sg = 0, sb = 0;
unsigned long n = 0;
for (int sy = sy0; sy < sy1; sy++) {
const uint32_t *row = (const uint32_t *)
((const char *)data + (size_t)sy * stride);
for (int sx = sx0; sx < sx1; sx++) {
uint32_t p = row[sx];
unsigned a = (p >> 24) & 0xff;
if (fmt == DRM_FORMAT_XRGB8888 || a == 0)
a = 0xff;
unsigned r = (p >> 16) & 0xff,
g = (p >> 8) & 0xff,
bl = p & 0xff;
sa += a;
sr += r * a / 255;
sg += g * a / 255;
sb += bl * a / 255;
n++;
}
}
if (n == 0) n = 1;
unsigned a = (unsigned)(sa / n);
unsigned r = (unsigned)(sr / n);
unsigned g = (unsigned)(sg / n);
unsigned bl = (unsigned)(sb / n);
w->thumb_px[(size_t)y * tw + x] =
(a << 24) | (r << 16) | (g << 8) | bl;
}
}
w->thumb_src = THUMB_SRC_SNAP;
w->thumb_dirty = true;
}
wlr_buffer_end_data_ptr_access(b);
if (w->thumb_buf) {
/* Source switched back to SHM: drop the GPU buffer lock. */
wlr_buffer_unlock(w->thumb_buf);
w->thumb_buf = NULL;
}
} else {
/* Readable but unexpected format: hold a lock and import. */
if (begun)
wlr_buffer_end_data_ptr_access(b);
if (b != w->thumb_buf) {
if (w->thumb_buf)
wlr_buffer_unlock(w->thumb_buf);
w->thumb_buf = wlr_buffer_lock(b);
}
w->thumb_src = THUMB_SRC_DMABUF;
w->thumb_dirty = true;
}
}
} else if (s && wlr_surface_get_texture(s)) {
/* In-place SHM update: the pixels went straight into the surface