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.
- [ ] 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 a basic bar
- [ ] 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);
if (buffer.busy) continue;
if (buffer.width == width and buffer.height == height) {
buffer.busy = true;
return buffer;
} else {
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| {
buffer.deinit();
buffer.* = try Buffer.init(wl_shm, width, height);
buffer.setListener();
return buffer;
}

View file

@ -223,7 +223,7 @@ pub fn initWallpaperLayerSurface(output: *Output) !void {
}
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;
}
@ -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 pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride);
if (pix == null) {
log.err("failed to copy the background image for rendering", .{});
const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride) orelse {
log.err("Failed to copy the wallpaper image for rendering", .{});
return error.ImageCopyError;
}
defer _ = pix.?.unref();
};
defer _ = pix.unref();
// Get scale for our image compared to the monitor's scale
// XXX: This sucks in Zig but also I'm sure there's a better way to write it
// Calculate image scale
var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale));
var sy: f64 = calculate_scale(image_height, height, scale);
@ -360,19 +358,31 @@ pub fn renderWallpaper(output: *Output) !void {
sx = 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 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;
// t2 is the fixed-point version of t, which is what pixman actually uses internally
var t2: pixman.Transform = undefined;
pixman.FTransform.initTranslate(&t, tx, ty);
pixman.FTransform.initScale(&t, sx, sy);
pixman.FTransform.initScale(&t_scale, sx, sy);
pixman.FTransform.initTranslate(&t_trans, tx, ty);
pixman.FTransform.multiply(&t, &t_trans, &t_scale);
_ = pixman.Transform.fromFTransform(&t2, &t);
_ = pix.?.setTransform(&t2);
_ = pix.?.setFilter(.best, &[_]pixman.Fixed{}, 0);
_ = pix.setTransform(&t2);
_ = 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 });

View file

@ -35,7 +35,7 @@ pub fn create(image_path: []const u8) !*WallpaperImage {
const g: u32 = @intCast(pixels[i].g);
const b: u32 = @intCast(pixels[i].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;

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 mem = std.mem;
const fatal = std.process.fatal;