Fix buffer pool race condition, pixman transform, and pixel conversion

Mark reused buffers as busy before returning from nextBuffer (before,
they only got marked busy on init).

Re-attach wl_buffer listener after re-initializing a buffer. This lets
the re-inited buffer still get a released event.

Combine scale and translate matrices with multiply instead of overwriting

Use appendAssumeCapacity for pixel conversion loop (since we already
initialized the list with the correct size).
This commit is contained in:
Ben Buhse 2026-02-08 10:48:13 -06:00
commit 225ddf0a53
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
5 changed files with 27 additions and 18 deletions

View file

@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
These are in rough order of my priority, though no promises I do them in this order. These are in rough order of my priority, though no promises I do them in this order.
- [ ] Switch all structs to idiomatic Zig init/deinit pattern (init returns value, caller decides stack/heap) - [ ] Switch all structs to idiomatic Zig init/deinit pattern (init returns value, caller decides stack/heap)
- [ ] Implement runtime log levels
- [ ] Support per-host config using properties - [ ] Support per-host config using properties
- [ ] Support a basic bar - [ ] Support a basic bar
- [ ] Support starting programs at WM launch - [ ] Support starting programs at WM launch

View file

@ -61,6 +61,7 @@ fn findSuitableBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, hei
const buffer: *Buffer = @fieldParentPtr("node", node); const buffer: *Buffer = @fieldParentPtr("node", node);
if (buffer.busy) continue; if (buffer.busy) continue;
if (buffer.width == width and buffer.height == height) { if (buffer.width == width and buffer.height == height) {
buffer.busy = true;
return buffer; return buffer;
} else { } else {
first_unbusy_buffer = buffer; first_unbusy_buffer = buffer;
@ -72,6 +73,7 @@ fn findSuitableBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, hei
if (first_unbusy_buffer) |buffer| { if (first_unbusy_buffer) |buffer| {
buffer.deinit(); buffer.deinit();
buffer.* = try Buffer.init(wl_shm, width, height); buffer.* = try Buffer.init(wl_shm, width, height);
buffer.setListener();
return buffer; return buffer;
} }

View file

@ -223,7 +223,7 @@ pub fn initWallpaperLayerSurface(output: *Output) !void {
} }
if (output.wl_surface) |_| { if (output.wl_surface) |_| {
log.debug("Skipping adding a second wallpaper surface to {s}", .{output.name orelse "some output"}); // This output already has a layer surface, we can exit early
return; return;
} }
@ -344,15 +344,13 @@ pub fn renderWallpaper(output: *Output) !void {
const buffer: *Buffer = try context.buffer_pool.nextBuffer(context.wl_shm, width * scale, height * scale); const buffer: *Buffer = try context.buffer_pool.nextBuffer(context.wl_shm, width * scale, height * scale);
const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride); const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride) orelse {
if (pix == null) { log.err("Failed to copy the wallpaper image for rendering", .{});
log.err("failed to copy the background image for rendering", .{});
return error.ImageCopyError; return error.ImageCopyError;
} };
defer _ = pix.?.unref(); defer _ = pix.unref();
// Get scale for our image compared to the monitor's scale // Calculate image scale
// XXX: This sucks in Zig but also I'm sure there's a better way to write it
var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale)); var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale));
var sy: f64 = calculate_scale(image_height, height, scale); var sy: f64 = calculate_scale(image_height, height, scale);
@ -360,19 +358,31 @@ pub fn renderWallpaper(output: *Output) !void {
sx = s; sx = s;
sy = s; sy = s;
// Calculate translation offsets to center the image on the output.
// If the scaled image is larger than the output, the offset crops equally from both sides.
const tx: f64 = calculate_transform(image_width, width, sx); const tx: f64 = calculate_transform(image_width, width, sx);
const ty: f64 = calculate_transform(image_height, height, sy); const ty: f64 = calculate_transform(image_height, height, sy);
// Build a combined source-to-destination transform matrix.
// Pixman transforms map destination pixels back to source pixels, so:
// t_scale: maps a destination pixel to the corresponding source pixel (scaling)
// t_trans: shifts the sampling point to center the image
// t = t_trans * t_scale: first scale, then translate (in source space)
var t_scale: pixman.FTransform = undefined;
var t_trans: pixman.FTransform = undefined;
var t: pixman.FTransform = undefined; var t: pixman.FTransform = undefined;
// t2 is the fixed-point version of t, which is what pixman actually uses internally
var t2: pixman.Transform = undefined; var t2: pixman.Transform = undefined;
pixman.FTransform.initTranslate(&t, tx, ty); pixman.FTransform.initScale(&t_scale, sx, sy);
pixman.FTransform.initScale(&t, sx, sy); pixman.FTransform.initTranslate(&t_trans, tx, ty);
pixman.FTransform.multiply(&t, &t_trans, &t_scale);
_ = pixman.Transform.fromFTransform(&t2, &t); _ = pixman.Transform.fromFTransform(&t2, &t);
_ = pix.?.setTransform(&t2); _ = pix.setTransform(&t2);
_ = pix.?.setFilter(.best, &[_]pixman.Fixed{}, 0); _ = pix.setFilter(.best, &[_]pixman.Fixed{}, 0);
pixman.Image.composite32(.src, pix.?, null, buffer.pixman_image, 0, 0, 0, 0, 0, 0, width * scale, height * scale); // Combine the transformed source image into the buffer.
pixman.Image.composite32(.src, pix, null, buffer.pixman_image, 0, 0, 0, 0, 0, 0, width * scale, height * scale);
log.info("render: {}x{} (scaled from {}x{})", .{ width * scale, height * scale, image_width, image_height }); log.info("render: {}x{} (scaled from {}x{})", .{ width * scale, height * scale, image_width, image_height });

View file

@ -35,7 +35,7 @@ pub fn create(image_path: []const u8) !*WallpaperImage {
const g: u32 = @intCast(pixels[i].g); const g: u32 = @intCast(pixels[i].g);
const b: u32 = @intCast(pixels[i].b); const b: u32 = @intCast(pixels[i].b);
const new_val: u32 = (a << 24) + (r << 16) + (g << 8) + b; const new_val: u32 = (a << 24) + (r << 16) + (g << 8) + b;
try wallpaper_image.pixels.append(utils.allocator, new_val); wallpaper_image.pixels.appendAssumeCapacity(new_val);
} }
wallpaper_image.image = pixman.Image.createBits(.a8r8g8b8, @intCast(image.width), @intCast(image.height), @ptrCast(@alignCast(wallpaper_image.pixels.items.ptr)), @intCast(image.width * image.pixelFormat().pixelStride())) orelse return error.FailedToCreatePixmanImage; wallpaper_image.image = pixman.Image.createBits(.a8r8g8b8, @intCast(image.width), @intCast(image.height), @ptrCast(@alignCast(wallpaper_image.pixels.items.ptr)), @intCast(image.width * image.pixelFormat().pixelStride())) orelse return error.FailedToCreatePixmanImage;

View file

@ -134,10 +134,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
} }
} }
pub const std_options = std.Options{
.log_level = .debug,
};
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const fatal = std.process.fatal; const fatal = std.process.fatal;