beansprout-custom/src/BufferPool.zig
Ben Buhse 95425aa73f
Rename utils.allocator to utils.gpa
it seems like `gpa` has become pretty much the universally agreed upon
name for your... gpa, so we're renaming.
2026-02-12 14:10:28 -06:00

124 lines
4.5 KiB
Zig

// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
//
// SPDX-License-Identifier: GPL-3.0-only
// Borrowed and adapted from https://git.sr.ht/~leon_plickat/wayprompt
const BufferPool = @This();
/// The amount of buffers per surface we consider the reasonable upper limit.
/// Some compositors sometimes tripple-buffer, so three seems to be ok.
/// Note that we can absolutely work with higher buffer numbers if needed,
/// however we consider that to be an anomaly and therefore do not want to
/// keep all those extra buffers around if we can avoid it, as to not have
/// unecessary memory overhead.
const max_buffer_multiplicity = 3;
/// The buffers. This is a linked list and not an array list, because we
/// need stable pointers for the listener of the wl_buffer object.
buffers: DoublyLinkedList = .{},
len: usize = 0,
/// Number of surfaces sharing this pool, used to determine when to cull extra buffers.
/// Each surface is allowed up to max_buffer_multiplicity buffers.
surface_count: usize = 0,
/// Deinit the buffer pool, destroying all buffers and freeing all memory.
pub fn deinit(buffer_pool: *BufferPool) void {
var it = buffer_pool.buffers.first;
while (it) |node| {
// Advance before destroying, since node is embedded in buffer
it = node.next;
const buffer: *Buffer = @fieldParentPtr("node", node);
buffer.deinit();
utils.gpa.destroy(buffer);
}
}
/// Get a buffer with the specified dimensions. If possible, an idle buffer is
/// reused, otherwise a new one is created.
pub fn nextBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !*Buffer {
log.debug("looking for buffer with dimensions {}x{}, total existing buffers: {}", .{ width, height, buffer_pool.len });
defer {
// Clear up extra buffers
if (buffer_pool.len > max_buffer_multiplicity * buffer_pool.surface_count) {
buffer_pool.cullBuffers();
}
}
if (try buffer_pool.findSuitableBuffer(wl_shm, width, height)) |buffer| {
return buffer;
} else {
return try buffer_pool.newBuffer(wl_shm, width, height);
}
}
/// Get the first free buffer with the specified dimensions.
/// If there are no free buffers with the right dimensions, re-init a free buffer that
/// has other dimensions. If no free buffer exists at all, return null.
fn findSuitableBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !?*Buffer {
var it = buffer_pool.buffers.first;
var first_unbusy_buffer: ?*Buffer = null;
while (it) |node| : (it = node.next) {
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;
}
}
// No buffer has matching dimensions, however we do have an unbusy
// buffer which we can just re-init.
if (first_unbusy_buffer) |buffer| {
buffer.deinit();
buffer.* = try Buffer.init(wl_shm, width, height);
buffer.setListener();
return buffer;
}
return null;
}
fn newBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !*Buffer {
log.debug("creating new buffer {}x{}", .{ width, height });
const buffer = try utils.gpa.create(Buffer);
errdefer utils.gpa.destroy(buffer);
buffer.* = try Buffer.init(wl_shm, width, height);
buffer.setListener();
buffer_pool.buffers.append(&buffer.node);
buffer_pool.len += 1;
return buffer;
}
fn cullBuffers(buffer_pool: *BufferPool) void {
log.debug("culling extra buffers", .{});
var overhead = buffer_pool.len - max_buffer_multiplicity * buffer_pool.surface_count;
var it = buffer_pool.buffers.first;
while (it) |node| {
if (overhead == 0) break;
// Advance before destroying, since node is embedded in buffer
it = node.next;
const buffer: *Buffer = @fieldParentPtr("node", node);
if (!buffer.busy) {
buffer.deinit();
buffer_pool.buffers.remove(node);
utils.gpa.destroy(buffer);
buffer_pool.len -= 1;
overhead -= 1;
}
}
log.debug(" -> new buffer count: {}", .{buffer_pool.len});
}
const std = @import("std");
const DoublyLinkedList = std.DoublyLinkedList;
const wayland = @import("wayland");
const wl = wayland.client.wl;
const Buffer = @import("Buffer.zig");
const utils = @import("utils.zig");
const log = std.log.scoped(.BufferPool);