feat: implement framebuffer allocator

dev
Louis Pearson 2024-02-16 15:11:15 -07:00
parent adadca6827
commit f22a9ac2a9
4 changed files with 549 additions and 53 deletions

View File

@ -160,7 +160,7 @@ pub const ShmPool = struct {
return .{ .conn = conn, .id = id };
}
pub fn create_buffer(shm_pool: ShmPool, offset: i32, width: i32, height: i32, stride: i32, format: Shm.Format) u32 {
pub fn create_buffer(shm_pool: ShmPool, offset: i32, width: i32, height: i32, stride: i32, format: Shm.Format) !Buffer {
const new_id = shm_pool.conn.id_pool.create();
try shm_pool.conn.send(
Request,
@ -174,7 +174,7 @@ pub const ShmPool = struct {
.format = format,
} },
);
return Surface.init(shm_pool.conn, new_id);
return Buffer.init(shm_pool.conn, new_id);
}
};
@ -216,7 +216,7 @@ pub const Shm = struct {
return .{ .conn = conn, .id = id };
}
pub fn create_pool(shm: *const Shm, pool_fd: u32, size: u32) ShmPool {
pub fn create_pool(shm: *const Shm, pool_fd: u32, size: u32) !ShmPool {
const new_id = shm.conn.id_pool.create();
try shm.conn.send(
Request,
@ -284,6 +284,31 @@ pub const Surface = struct {
return .{ .conn = conn, .id = id };
}
pub fn attach(surface: Surface, buffer: Buffer, x: i32, y: i32) !void {
try surface.conn.send(
Request,
surface.id,
.{ .attach = .{
.buffer = buffer.id,
.x = x,
.y = y,
} },
);
}
pub fn damage(surface: Surface, x: i32, y: i32, width: i32, height: i32) !void {
try surface.conn.send(
Request,
surface.id,
.{ .damage = .{
.x = x,
.y = y,
.width = width,
.height = height,
} },
);
}
pub fn commit(surface: Surface) !void {
try surface.conn.send(
Request,
@ -301,6 +326,13 @@ pub const Buffer = struct {
pub const Event = union(enum) {
release: void,
};
conn: *Conn,
id: u32,
pub fn init(conn: *Conn, id: u32) Buffer {
return .{ .conn = conn, .id = id };
}
};
pub const Seat = struct {
@ -344,6 +376,40 @@ pub const Seat = struct {
pub fn init(conn: *Conn, id: u32) Seat {
return .{ .conn = conn, .id = id };
}
pub fn get_pointer(seat: Seat) !Pointer {
const new_id = seat.conn.id_pool.create();
try seat.conn.send(
Request,
seat.id,
.{ .get_pointer = .{
.new_id = new_id,
} },
);
return Pointer.init(seat.conn, new_id);
}
pub fn get_keyboard(seat: Seat) !Keyboard {
const new_id = seat.conn.id_pool.create();
try seat.conn.send(
Request,
seat.id,
.{ .get_keyboard = .{
.new_id = new_id,
} },
);
return Keyboard.init(seat.conn, new_id);
}
pub fn get_touch(seat: Seat) !Touch {
const new_id = seat.conn.id_pool.create();
try seat.conn.send(
Request,
seat.id,
.{ .get_touch = .{
.new_id = new_id,
} },
);
return Touch.init(seat.conn, new_id);
}
};
pub const Pointer = struct {
@ -435,7 +501,7 @@ pub const Pointer = struct {
conn: *Conn,
id: u32,
pub fn init(conn: *Conn, id: u32) Seat {
pub fn init(conn: *Conn, id: u32) Pointer {
return .{ .conn = conn, .id = id };
}
};
@ -492,7 +558,7 @@ pub const Keyboard = struct {
conn: *Conn,
id: u32,
pub fn init(conn: *Conn, id: u32) Seat {
pub fn init(conn: *Conn, id: u32) Keyboard {
return .{ .conn = conn, .id = id };
}
};
@ -538,7 +604,7 @@ pub const Touch = struct {
conn: *Conn,
id: u32,
pub fn init(conn: *Conn, id: u32) Seat {
pub fn init(conn: *Conn, id: u32) Touch {
return .{ .conn = conn, .id = id };
}
};

View File

@ -63,6 +63,7 @@ pub fn main() !void {
var conn = try Conn.init(gpa, display_path);
defer conn.deinit();
// Register all globals
var ctx = try Context.init(&conn);
const compositor = try ctx.getGlobal(wayland.core.Compositor);
@ -75,58 +76,468 @@ pub fn main() !void {
try surface.commit();
const display = try ctx.getGlobal(wayland.core.Display);
const registry_done = try display.sync();
const shm = try ctx.getGlobal(wayland.core.Shm);
const seat = try ctx.getGlobal(wayland.core.Seat);
var done = false;
var surface_configured = false;
var seat_capabilties: ?wayland.core.Seat.Capability = null;
while (!done or !surface_configured) {
var app = App{
.conn = &conn,
.ctx = &ctx,
.ally = gpa,
.callbacks = std.AutoHashMap(u32, App.Callback).init(gpa),
.state = .{ .init = .{} },
.display = display,
.shm = shm,
.xdg_wmbase = xdg_wm_base,
.surface = surface,
.xdg_surface = xdg_surface,
.xdg_toplevel = xdg_toplevel,
};
defer app.deinit();
try app.callbacks.put(seat.id, seatInitHandler);
try app.callbacks.put(xdg_toplevel.id, toplevelInitHandler);
try app.callbacks.put(xdg_surface.id, surfaceInitHandler);
try app.callbacks.put(display.id, displayHandler);
while (app.state == .init) {
const header, const body = try conn.recv();
if (header.object_id == xdg_surface.id) {
if (app.callbacks.get(header.object_id)) |callback| {
try callback(&app, header, body);
} else {
const typename = if (ctx.getGlobalObjectName(header.object_id)) |name|
name
else
"UNKNOWN";
std.debug.print("{} ({s}) {x} \"{}\"\n", .{
header.object_id,
typename,
header.size_and_opcode.opcode,
std.zig.fmtEscapes(std.mem.sliceAsBytes(body)),
});
}
}
const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool;
const width = if (app.state == .run_mobile) app.state.run_mobile.width else app.state.run_desktop.width;
const height = if (app.state == .run_mobile) app.state.run_mobile.height else app.state.run_desktop.height;
// render
const canvas = try pool.getFramebuffer(.{ width, height });
canvas.renderGradient();
try canvas.attach(surface);
try app.callbacks.put(canvas.buffer.id, bufferHandler);
while (app.state != .close) {
const header, const body = try conn.recv();
if (app.callbacks.get(header.object_id)) |callback| {
try callback(&app, header, body);
} else {
const typename = if (ctx.getGlobalObjectName(header.object_id)) |name|
name
else
"UNKNOWN";
std.debug.print("{} ({s}) {x} \"{}\"\n", .{
header.object_id,
typename,
header.size_and_opcode.opcode,
std.zig.fmtEscapes(std.mem.sliceAsBytes(body)),
});
}
}
}
const App = struct {
conn: *Conn,
ctx: *Context,
ally: std.mem.Allocator,
callbacks: std.AutoHashMap(u32, Callback),
display: wayland.core.Display,
shm: wayland.core.Shm,
surface: wayland.core.Surface,
xdg_wmbase: wayland.xdg.WmBase,
xdg_surface: wayland.xdg.Surface,
xdg_toplevel: wayland.xdg.Toplevel,
state: union(enum) {
init: struct {
toplevel_config: ?SurfaceConfiguration = null,
surface_configured: bool = false,
seat_capabilities: ?wayland.core.Seat.Capability = null,
pointer: ?wayland.core.Pointer = null,
keyboard: ?wayland.core.Keyboard = null,
touch: ?wayland.core.Touch = null,
},
run_mobile: struct {
width: u32,
height: u32,
pool: Pool,
touch: wayland.core.Touch,
},
run_desktop: struct {
width: u32,
height: u32,
pool: Pool,
keyboard: wayland.core.Keyboard,
pointer: wayland.core.Pointer,
mouse: struct {
x: f32,
y: f32,
},
},
close,
},
const SurfaceConfiguration = struct {
width: u32,
height: u32,
};
const Callback = *const fn (*App, wayland.Header, []const u32) anyerror!void;
fn deinit(app: *App) void {
app.callbacks.deinit();
}
fn checkInit(app: *App) !void {
std.debug.assert(app.state == .init);
const init = app.state.init;
if (!init.surface_configured) return;
if (init.seat_capabilities == null) return;
app.callbacks.clearRetainingCapacity();
try app.callbacks.put(app.display.id, displayHandler);
if (init.touch != null and init.keyboard == null) {
// mobile
app.state = .{ .run_mobile = .{
.width = init.toplevel_config.?.width,
.height = init.toplevel_config.?.height,
.pool = try Pool.init(app.ally, app.shm),
.touch = init.touch.?,
} };
try app.callbacks.put(app.xdg_wmbase.id, wmbaseHandler);
try app.callbacks.put(app.xdg_toplevel.id, toplevelHandler);
// try app.callbacks.put(app.state.run_desktop.touch.id, touchMobileHandler);
} else if (init.keyboard != null and init.pointer != null) {
// desktop
app.state = .{ .run_desktop = .{
.width = init.toplevel_config.?.width,
.height = init.toplevel_config.?.height,
.pool = try Pool.init(app.ally, app.shm),
.keyboard = init.keyboard.?,
.pointer = init.pointer.?,
.mouse = .{ .x = 0, .y = 0 },
} };
try app.callbacks.put(app.state.run_desktop.pointer.id, pointerDesktopHandler);
try app.callbacks.put(app.state.run_desktop.keyboard.id, keyboardDesktopHandler);
try app.callbacks.put(app.xdg_surface.id, surfaceDesktopHandler);
try app.callbacks.put(app.xdg_wmbase.id, wmbaseHandler);
try app.callbacks.put(app.xdg_toplevel.id, toplevelHandler);
} else {
@panic("no keyboard or touch");
}
}
};
fn surfaceInitHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try xdg_surface.ack_configure(conf.serial);
surface_configured = true;
try app.xdg_surface.ack_configure(conf.serial);
std.debug.assert(app.state.init.toplevel_config != null);
app.state.init.surface_configured = true;
try app.checkInit();
},
}
} else if (header.object_id == xdg_toplevel.id) {
}
fn toplevelInitHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body);
std.debug.print("<- {}\n", .{event});
} else if (header.object_id == registry_done) {
done = true;
} else if (header.object_id == shm.id) {
const event = try wayland.deserialize(wayland.core.Shm.Event, header, body);
switch (event) {
.format => |format| std.debug.print("<- format {} {}\n", .{ format.format, std.zig.fmtEscapes(std.mem.asBytes(&format.format)) }),
.configure => |conf| {
var width = conf.width;
var height = conf.height;
if (conf.width == 0) {
width = 128;
height = 128;
}
} else if (header.object_id == seat.id) {
app.state.init.toplevel_config = .{
.width = @intCast(width),
.height = @intCast(height),
};
},
else => {
std.log.info("toplevel event: {}", .{event});
},
}
}
fn seatInitHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.core.Seat.Event, header, body);
const seat = wayland.core.Seat.init(app.conn, header.object_id); // TODO: pass into cb
switch (event) {
.capabilities => |capabilities| {
const cap: wayland.core.Seat.Capability = @bitCast(capabilities.capability);
std.debug.print("<- wl_seat.capabilties = {}\n", .{cap});
seat_capabilties = cap;
const caps: wayland.core.Seat.Capability = @bitCast(capabilities.capability);
app.state.init.seat_capabilities = caps;
if (caps.pointer) {
app.state.init.pointer = try seat.get_pointer();
}
if (caps.touch) {
app.state.init.touch = try seat.get_touch();
}
if (caps.keyboard) {
app.state.init.keyboard = try seat.get_keyboard();
}
try app.checkInit();
},
.name => |name| {
std.debug.print("<- wl_seat.name = {s}\n", .{name.name});
},
}
} else if (header.object_id == display.id) {
}
fn displayHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.core.Display.Event, header, body);
switch (event) {
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
.delete_id => |id| {
std.debug.print("id {} deleted\n", .{id});
conn.id_pool.destroy(id.id);
app.conn.id_pool.destroy(id.id);
},
}
} else {
std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(body)) });
}
const Pool = struct {
fd: std.os.fd_t,
shm: wayland.core.ShmPool,
mem: []u8,
file_length: usize,
allocationTable: std.AutoHashMap(u32, Buffer),
const Buffer = struct {
offset: usize,
len: usize,
width: usize = 0,
height: usize = 0,
};
// Memory to store 4k screen pixels
// const UPPER_BOUND = 33_177_600;
// Memory to store 8k screen pixels
// const UPPER_BOUND = 132_710_400;
const UPPER_BOUND = (7680 * 4320 * 4);
fn init(allocator: std.mem.Allocator, shm: wayland.core.Shm) !Pool {
const pool_fd_1 = try std.os.memfd_create("my-wayland-framebuffer-1", 0);
const pool_bytes_1 = try std.os.mmap(
null,
UPPER_BOUND,
std.os.PROT.READ | std.os.PROT.WRITE,
std.os.MAP.SHARED,
pool_fd_1,
0,
);
const shm1 = try shm.create_pool(@intCast(pool_fd_1), @intCast(pool_bytes_1.len));
return Pool{
.fd = pool_fd_1,
.shm = shm1,
.mem = pool_bytes_1,
.file_length = 0,
.allocationTable = std.AutoHashMap(u32, Buffer).init(allocator),
};
}
fn getFramebuffer(pool: *Pool, size: [2]u32) !Canvas {
var iter = pool.allocationTable.iterator();
var first_offset: usize = std.math.maxInt(usize);
var total_len: usize = 0;
while (iter.next()) |entry| {
const offset = entry.value_ptr.offset;
const len = entry.value_ptr.len;
total_len += len;
if (offset < first_offset) {
if (first_offset != std.math.maxInt(usize)) {
total_len += first_offset - offset;
}
first_offset = offset;
}
}
const pixel_count = size[0] * size[1];
const byte_count = pixel_count * @sizeOf(Pixel);
var alloc_offset: usize = 0;
if (byte_count < first_offset) {
alloc_offset = 0;
} else {
alloc_offset = first_offset + total_len;
}
const final_offset = alloc_offset + byte_count;
if (final_offset > pool.file_length) {
try std.os.ftruncate(pool.fd, final_offset);
pool.file_length = final_offset;
}
const buffer = try pool.shm.create_buffer(
@intCast(alloc_offset),
@intCast(size[0]),
@intCast(size[1]),
@intCast(size[0] * @sizeOf(Pixel)),
.argb8888,
);
try pool.allocationTable.put(buffer.id, .{
.offset = alloc_offset,
.len = byte_count,
.width = size[0],
.height = size[1],
});
return .{
.pool = pool,
.buffer = buffer,
.fb = @as([*]Pixel, @ptrCast(pool.mem[alloc_offset..].ptr))[0..pixel_count],
.size = size,
};
}
fn freeFramebuffer(pool: *Pool, buffer_id: u32) !void {
std.debug.assert(pool.allocationTable.remove(buffer_id));
}
};
const Canvas = struct {
pool: *Pool,
fb: []Pixel,
buffer: wayland.core.Buffer,
size: [2]u32,
fn attach(canvas: Canvas, surface: wayland.core.Surface) !void {
try surface.attach(canvas.buffer, 0, 0);
try surface.damage(0, 0, std.math.maxInt(i32), std.math.maxInt(i32));
try surface.commit();
}
fn renderGradient(canvas: Canvas) void {
const fb = canvas.fb;
const width = canvas.size[0];
const height = canvas.size[1];
for (0..height) |y| {
const row = fb[y * width .. (y + 1) * width];
for (row, 0..width) |*pixel, x| {
pixel.* = .{
@truncate(x),
@truncate(y),
0x00,
0xFF,
};
}
}
}
};
fn wmbaseHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.xdg.WmBase.Event, header, body);
switch (event) {
.ping => |ping| {
try app.xdg_wmbase.pong(ping.serial);
},
}
}
fn toplevelHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body);
switch (event) {
.configure => |conf| {
var width = conf.width;
var height = conf.height;
if (conf.width == 0) {
width = 128;
height = 128;
}
if (app.state == .run_mobile) {
app.state.run_mobile.width = @intCast(width);
app.state.run_mobile.height = @intCast(height);
} else {
app.state.run_desktop.width = @intCast(width);
app.state.run_desktop.height = @intCast(height);
}
},
else => {
std.log.info("toplevel event: {}", .{event});
},
}
}
fn surfaceDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try app.xdg_surface.ack_configure(conf.serial);
const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool;
const width = if (app.state == .run_mobile) app.state.run_mobile.width else app.state.run_desktop.width;
const height = if (app.state == .run_mobile) app.state.run_mobile.height else app.state.run_desktop.height;
// render
const canvas = try pool.getFramebuffer(.{ width, height });
canvas.renderGradient();
try canvas.attach(app.surface);
try app.callbacks.put(canvas.buffer.id, bufferHandler);
},
}
}
fn pointerDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.core.Pointer.Event, header, body);
switch (event) {
.enter => |enter| {
app.state.run_desktop.mouse.x = @floatFromInt(enter.surface_x);
app.state.run_desktop.mouse.y = @floatFromInt(enter.surface_y);
},
.leave => |_| {},
.motion => |motion| {
app.state.run_desktop.mouse.x = @floatFromInt(motion.surface_x);
app.state.run_desktop.mouse.y = @floatFromInt(motion.surface_y);
},
else => {
std.log.info("pointer event: {}", .{event});
},
// .button => |button| { _ = button; },
// .axis => |axis| { _ = axis;},
// .frame => {},
// .axis_source => |axis_source| { _ = axis_source; },
// .axis_stop => |axis_stop| { _ = axis_stop; },
// .axis_discrete => |axis_discrete| { _ = axis_discrete; },
// .axis_value120 => |axis_value120| { _ = axis_value120; },
// .axis_relative_direction => |axis_relative_direction| {_ = axis_relative_direction; },
}
}
fn keyboardDesktopHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.core.Keyboard.Event, header, body);
_ = app;
switch (event) {
else => {
std.log.info("keyboard event: {}", .{event});
},
// .button => |button| { _ = button; },
// .axis => |axis| { _ = axis;},
// .frame => {},
// .axis_source => |axis_source| { _ = axis_source; },
// .axis_stop => |axis_stop| { _ = axis_stop; },
// .axis_discrete => |axis_discrete| { _ = axis_discrete; },
// .axis_value120 => |axis_value120| { _ = axis_value120; },
// .axis_relative_direction => |axis_relative_direction| {_ = axis_relative_direction; },
}
}
fn bufferHandler(app: *App, header: wayland.Header, body: []const u32) !void {
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body);
std.debug.assert(event == .release);
const pool = if (app.state == .run_mobile) &app.state.run_mobile.pool else &app.state.run_desktop.pool;
try pool.freeFramebuffer(header.object_id);
}

View File

@ -557,8 +557,10 @@ pub fn Context(comptime T: []const type) type {
const Item = struct { version: u32, index: u32 };
const Pair = struct { []const u8, Item };
comptime var kvs_list: []const Pair = &[_]Pair{};
comptime var name_list: []const []const u8 = &[_][]const u8{};
inline for (T, 0..) |t, i| {
kvs_list = kvs_list ++ &[_]Pair{.{ t.INTERFACE, .{ .version = t.VERSION, .index = i } }};
name_list = name_list ++ &[_][]const u8{t.INTERFACE};
}
const stringToType = std.ComptimeStringMap(Item, kvs_list);
@ -566,6 +568,8 @@ pub fn Context(comptime T: []const type) type {
conn: *Conn,
global_ids: [T.len]?u32,
const global_names = name_list;
pub fn init(conn: *Conn) !@This() {
var ctx = @This(){
.conn = conn,
@ -575,6 +579,14 @@ pub fn Context(comptime T: []const type) type {
return ctx;
}
pub fn getGlobalObjectName(ctx: *@This(), id: u32) ?[]const u8 {
for (ctx.global_ids, 0..) |gid_opt, i| {
const gid = gid_opt orelse continue;
if (gid == id) return global_names[i];
}
return null;
}
pub fn getGlobal(ctx: *@This(), comptime G: type) !G {
if (G == core.Display) return .{ .id = 1, .conn = ctx.conn };
const g = stringToType.get(G.INTERFACE) orelse return error.NoSuchGlobal;
@ -598,7 +610,6 @@ pub fn Context(comptime T: []const type) type {
try ctx.conn.socket.writeAll(std.mem.sliceAsBytes(message));
}
// var ids: [T.len]?u32 = [_]?u32{null} ** T.len;
var message_buffer = std.ArrayList(u32).init(ctx.conn.allocator);
defer message_buffer.deinit();
while (true) {
@ -636,7 +647,7 @@ pub fn Context(comptime T: []const type) type {
} else if (header.object_id == registry_done_id) {
break;
} else {
std.log.info("{} {x} \"{}\"", .{
std.log.info("registerGlobals: {} {x} \"{}\"", .{
header.object_id,
header.size_and_opcode.opcode,
std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)),

View File

@ -63,6 +63,14 @@ pub const WmBase = struct {
);
return Surface.init(base.conn, new_id);
}
pub fn pong(base: WmBase, serial: u32) !void {
try base.conn.send(
Request,
base.id,
.{ .pong = .{ .serial = serial } },
);
}
};
pub const Surface = struct {