196 lines
6.6 KiB
Zig
196 lines
6.6 KiB
Zig
//! Minimal wl_shm buffer pool for the tab bar.
|
|
//!
|
|
//! The tab bar is the only thing att_wm draws itself, and it draws nothing but
|
|
//! solid rectangles and text in the bitmap font from `font.zig`, so this
|
|
//! deliberately stops at "memfd, mmap, fill" rather than pulling in pixman.
|
|
|
|
const std = @import("std");
|
|
const posix = std.posix;
|
|
const Allocator = std.mem.Allocator;
|
|
const sys = @import("sys.zig");
|
|
|
|
const wayland = @import("wayland");
|
|
const wl = wayland.client.wl;
|
|
|
|
const font = @import("font.zig");
|
|
const layout = @import("layout.zig");
|
|
const Box = layout.Box;
|
|
|
|
/// Two buffers is enough: we redraw at most once per render sequence and the
|
|
/// compositor releases the previous one promptly.
|
|
const buffer_count = 2;
|
|
|
|
pub const Buffer = struct {
|
|
wl_buffer: *wl.Buffer,
|
|
data: []align(std.heap.page_size_min) u8,
|
|
width: i32,
|
|
height: i32,
|
|
/// Held by the compositor; must not be drawn into until released.
|
|
busy: bool = false,
|
|
|
|
fn onRelease(_: *wl.Buffer, event: wl.Buffer.Event, self: *Buffer) void {
|
|
switch (event) {
|
|
.release => self.busy = false,
|
|
}
|
|
}
|
|
|
|
pub fn pixels(self: *Buffer) []u32 {
|
|
const count: usize = @intCast(self.width * self.height);
|
|
const ptr: [*]u32 = @ptrCast(@alignCast(self.data.ptr));
|
|
return ptr[0..count];
|
|
}
|
|
|
|
/// Fill a rectangle, in buffer-local coordinates, clipped to the buffer.
|
|
pub fn fill(self: *Buffer, rect: Box, argb: u32) void {
|
|
const x0 = @max(0, rect.x);
|
|
const y0 = @max(0, rect.y);
|
|
const x1 = @min(self.width, rect.x + rect.width);
|
|
const y1 = @min(self.height, rect.y + rect.height);
|
|
if (x1 <= x0 or y1 <= y0) return;
|
|
|
|
const px = self.pixels();
|
|
const stride: usize = @intCast(self.width);
|
|
var y: i32 = y0;
|
|
while (y < y1) : (y += 1) {
|
|
const row_start = @as(usize, @intCast(y)) * stride;
|
|
const from = row_start + @as(usize, @intCast(x0));
|
|
const to = row_start + @as(usize, @intCast(x1));
|
|
@memset(px[from..to], argb);
|
|
}
|
|
}
|
|
|
|
/// Fill a rectangle, clipped to `clip` as well as to the buffer.
|
|
pub fn fillClipped(self: *Buffer, rect: Box, clip: Box, argb: u32) void {
|
|
const x0 = @max(rect.x, clip.x);
|
|
const y0 = @max(rect.y, clip.y);
|
|
const x1 = @min(rect.x + rect.width, clip.x + clip.width);
|
|
const y1 = @min(rect.y + rect.height, clip.y + clip.height);
|
|
if (x1 <= x0 or y1 <= y0) return;
|
|
self.fill(.{ .x = x0, .y = y0, .width = x1 - x0, .height = y1 - y0 }, argb);
|
|
}
|
|
|
|
/// Draw text with the top left of its first cell at (x, y), clipped to
|
|
/// `clip`, and return the x the next cell would start at. The font has no
|
|
/// antialiasing, so lit pixels are simply written: nothing to blend.
|
|
pub fn drawText(self: *Buffer, text: []const u8, x: i32, y: i32, scale: i32, argb: u32, clip: Box) i32 {
|
|
var pen = x;
|
|
var it: font.Iterator = .{ .bytes = text };
|
|
while (it.next()) |cp| {
|
|
// Stop as soon as the pen leaves the clip rather than walking the
|
|
// rest of a long title a pixel at a time.
|
|
if (pen >= clip.x + clip.width) break;
|
|
self.drawGlyph(cp, pen, y, scale, argb, clip);
|
|
pen += font.advance * scale;
|
|
}
|
|
return pen;
|
|
}
|
|
|
|
pub fn drawGlyph(self: *Buffer, cp: u21, x: i32, y: i32, scale: i32, argb: u32, clip: Box) void {
|
|
const rows = font.glyph(cp);
|
|
for (rows, 0..) |bits, row| {
|
|
if (bits == 0) continue;
|
|
const py = y + @as(i32, @intCast(row)) * scale;
|
|
var col: u3 = 0;
|
|
while (col < font.cell_width) : (col += 1) {
|
|
const mask = @as(u8, 1) << @intCast(font.cell_width - 1 - col);
|
|
if (bits & mask == 0) continue;
|
|
self.fillClipped(.{
|
|
.x = x + @as(i32, col) * scale,
|
|
.y = py,
|
|
.width = scale,
|
|
.height = scale,
|
|
}, clip, argb);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn deinit(self: *Buffer, gpa: Allocator) void {
|
|
self.wl_buffer.destroy();
|
|
posix.munmap(self.data);
|
|
gpa.destroy(self);
|
|
}
|
|
};
|
|
|
|
pub const Pool = struct {
|
|
gpa: Allocator,
|
|
shm: *wl.Shm,
|
|
buffers: [buffer_count]?*Buffer = @splat(null),
|
|
|
|
pub fn init(gpa: Allocator, shm: *wl.Shm) Pool {
|
|
return .{ .gpa = gpa, .shm = shm };
|
|
}
|
|
|
|
pub fn deinit(self: *Pool) void {
|
|
for (&self.buffers) |*slot| {
|
|
if (slot.*) |buf| buf.deinit(self.gpa);
|
|
slot.* = null;
|
|
}
|
|
}
|
|
|
|
/// Return a buffer of the requested size that the compositor is not
|
|
/// currently reading from, creating or resizing one as needed.
|
|
pub fn acquire(self: *Pool, width: i32, height: i32) !*Buffer {
|
|
if (width <= 0 or height <= 0) return error.InvalidSize;
|
|
|
|
// Reuse an idle buffer that is already the right size.
|
|
for (self.buffers) |maybe| {
|
|
if (maybe) |buf| {
|
|
if (!buf.busy and buf.width == width and buf.height == height) return buf;
|
|
}
|
|
}
|
|
|
|
// Otherwise take a free slot, evicting an idle wrong-sized buffer.
|
|
for (&self.buffers) |*slot| {
|
|
if (slot.* == null) {
|
|
slot.* = try self.create(width, height);
|
|
return slot.*.?;
|
|
}
|
|
}
|
|
for (&self.buffers) |*slot| {
|
|
const buf = slot.*.?;
|
|
if (!buf.busy) {
|
|
buf.deinit(self.gpa);
|
|
slot.* = try self.create(width, height);
|
|
return slot.*.?;
|
|
}
|
|
}
|
|
|
|
return error.AllBuffersBusy;
|
|
}
|
|
|
|
fn create(self: *Pool, width: i32, height: i32) !*Buffer {
|
|
const stride = width * 4;
|
|
const size: usize = @intCast(stride * height);
|
|
|
|
const fd = try posix.memfd_create("att_wm-shm", std.os.linux.MFD.CLOEXEC);
|
|
defer sys.close(fd);
|
|
try sys.ftruncate(fd, size);
|
|
|
|
const data = try posix.mmap(
|
|
null,
|
|
size,
|
|
.{ .READ = true, .WRITE = true },
|
|
.{ .TYPE = .SHARED },
|
|
fd,
|
|
0,
|
|
);
|
|
errdefer posix.munmap(data);
|
|
|
|
const shm_pool = try self.shm.createPool(fd, @intCast(size));
|
|
defer shm_pool.destroy();
|
|
|
|
const wl_buffer = try shm_pool.createBuffer(0, width, height, stride, .argb8888);
|
|
errdefer wl_buffer.destroy();
|
|
|
|
const buf = try self.gpa.create(Buffer);
|
|
buf.* = .{
|
|
.wl_buffer = wl_buffer,
|
|
.data = data,
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
wl_buffer.setListener(*Buffer, Buffer.onRelease, buf);
|
|
return buf;
|
|
}
|
|
};
|