fix(gui): resolve crashes and glyph rendering bugs from M3 smoke test
This commit is contained in:
parent
996e6714ba
commit
4291ccf2c5
4 changed files with 105 additions and 66 deletions
|
|
@ -24,9 +24,9 @@ const log = std.log.scoped(.wio_app);
|
|||
|
||||
const ScreenSnapshot = struct {
|
||||
cells: []gpu.Cell,
|
||||
codepoints: []u21,
|
||||
width: u16,
|
||||
height: u16,
|
||||
font: gpu.Font,
|
||||
};
|
||||
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
|
||||
|
|
@ -37,6 +37,10 @@ var tui_pid: thespian.pid = undefined;
|
|||
var font_size_px: u16 = 16;
|
||||
var font_name_buf: [256]u8 = undefined;
|
||||
var font_name_len: usize = 0;
|
||||
var font_dirty: std.atomic.Value(bool) = .init(true);
|
||||
|
||||
// Current font — written and read only from the wio thread (after gpu.init).
|
||||
var wio_font: gpu.Font = .{ .cell_size = .{ .x = 8, .y = 16 } };
|
||||
|
||||
// ── Public API (called from tui thread) ───────────────────────────────────
|
||||
|
||||
|
|
@ -58,28 +62,39 @@ pub fn updateScreen(vx_screen: *const vaxis.Screen) void {
|
|||
const cell_count: usize = @as(usize, vx_screen.width) * @as(usize, vx_screen.height);
|
||||
|
||||
const new_cells = allocator.alloc(gpu.Cell, cell_count) catch return;
|
||||
const new_font = getFont();
|
||||
const new_codepoints = allocator.alloc(u21, cell_count) catch {
|
||||
allocator.free(new_cells);
|
||||
return;
|
||||
};
|
||||
|
||||
// Convert vaxis cells → gpu.Cell (glyph + colours)
|
||||
// Glyph indices are filled in on the GPU thread; here we just store 0.
|
||||
for (vx_screen.buf[0..cell_count], new_cells) |*vc, *gc| {
|
||||
// Convert vaxis cells → gpu.Cell (colours only; glyph indices filled on GPU thread).
|
||||
for (vx_screen.buf[0..cell_count], new_cells, new_codepoints) |*vc, *gc, *cp| {
|
||||
gc.* = .{
|
||||
.glyph_index = 0,
|
||||
.background = colorFromVaxis(vc.style.bg),
|
||||
.foreground = colorFromVaxis(vc.style.fg),
|
||||
};
|
||||
// Decode first codepoint from the grapheme cluster.
|
||||
const g = vc.char.grapheme;
|
||||
cp.* = if (g.len > 0) blk: {
|
||||
const seq_len = std.unicode.utf8ByteSequenceLength(g[0]) catch break :blk ' ';
|
||||
break :blk std.unicode.utf8Decode(g[0..@min(seq_len, g.len)]) catch ' ';
|
||||
} else ' ';
|
||||
}
|
||||
|
||||
screen_mutex.lock();
|
||||
defer screen_mutex.unlock();
|
||||
|
||||
// Free the previous snapshot
|
||||
if (screen_snap) |old| allocator.free(old.cells);
|
||||
if (screen_snap) |old| {
|
||||
allocator.free(old.cells);
|
||||
allocator.free(old.codepoints);
|
||||
}
|
||||
screen_snap = .{
|
||||
.cells = new_cells,
|
||||
.codepoints = new_codepoints,
|
||||
.width = vx_screen.width,
|
||||
.height = vx_screen.height,
|
||||
.font = new_font,
|
||||
};
|
||||
|
||||
screen_pending.store(true, .release);
|
||||
|
|
@ -93,6 +108,7 @@ pub fn requestRender() void {
|
|||
|
||||
pub fn setFontSize(size_px: f32) void {
|
||||
font_size_px = @intFromFloat(@max(4, size_px));
|
||||
font_dirty.store(true, .release);
|
||||
requestRender();
|
||||
}
|
||||
|
||||
|
|
@ -105,14 +121,24 @@ pub fn setFontFace(name: []const u8) void {
|
|||
const copy_len = @min(name.len, font_name_buf.len);
|
||||
@memcpy(font_name_buf[0..copy_len], name[0..copy_len]);
|
||||
font_name_len = copy_len;
|
||||
font_dirty.store(true, .release);
|
||||
requestRender();
|
||||
}
|
||||
|
||||
// ── Internal helpers ──────────────────────────────────────────────────────
|
||||
// ── Internal helpers (wio thread only) ────────────────────────────────────
|
||||
|
||||
fn getFont() gpu.Font {
|
||||
// Reload wio_font from current settings. Called only from the wio thread.
|
||||
fn reloadFont() void {
|
||||
const name = if (font_name_len > 0) font_name_buf[0..font_name_len] else "monospace";
|
||||
return gpu.loadFont(name, font_size_px) catch gpu.Font{ .cell_size = .{ .x = 8, .y = 16 } };
|
||||
wio_font = gpu.loadFont(name, font_size_px) catch return;
|
||||
}
|
||||
|
||||
// Check dirty flag and reload if needed.
|
||||
fn maybeReloadFont(win_size: wio.Size, state: *gpu.WindowState, cell_width: *u16, cell_height: *u16) void {
|
||||
if (font_dirty.swap(false, .acq_rel)) {
|
||||
reloadFont();
|
||||
sendResize(win_size, state, cell_width, cell_height);
|
||||
}
|
||||
}
|
||||
|
||||
fn colorFromVaxis(color: vaxis.Cell.Color) gpu.Color {
|
||||
|
|
@ -174,6 +200,9 @@ fn wioLoop() void {
|
|||
};
|
||||
defer gpu.deinit();
|
||||
|
||||
// Load the initial font on the wio thread (gpu.init must be done first).
|
||||
reloadFont();
|
||||
|
||||
var state = gpu.WindowState.init();
|
||||
defer state.deinit();
|
||||
|
||||
|
|
@ -194,6 +223,9 @@ fn wioLoop() void {
|
|||
while (running) {
|
||||
wio.wait(.{});
|
||||
|
||||
// Reload font if settings changed (font_dirty set by TUI thread).
|
||||
maybeReloadFont(win_size, &state, &cell_width, &cell_height);
|
||||
|
||||
while (window.getEvent()) |event| {
|
||||
switch (event) {
|
||||
.close => {
|
||||
|
|
@ -209,11 +241,10 @@ fn wioLoop() void {
|
|||
if (input_translate.mouseButtonId(btn)) |mb_id| {
|
||||
const col: i32 = @intCast(mouse_pos.x);
|
||||
const row: i32 = @intCast(mouse_pos.y);
|
||||
const font = getFont();
|
||||
const col_cell: i32 = @intCast(@divTrunc(col, font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(row, font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(col, font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(row, font.cell_size.y));
|
||||
const col_cell: i32 = @intCast(@divTrunc(col, wio_font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(row, wio_font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(col, wio_font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(row, wio_font.cell_size.y));
|
||||
tui_pid.send(.{
|
||||
"RDR", "B",
|
||||
@as(u8, 1), // press
|
||||
|
|
@ -225,14 +256,14 @@ fn wioLoop() void {
|
|||
}) catch {};
|
||||
} else {
|
||||
const cp = input_translate.codepointFromButton(btn, mods);
|
||||
sendKey(1, cp, cp, mods);
|
||||
if (cp != 0) sendKey(1, cp, cp, mods);
|
||||
}
|
||||
},
|
||||
.button_repeat => |btn| {
|
||||
const mods = input_translate.Mods.fromButtons(held_buttons);
|
||||
if (input_translate.mouseButtonId(btn) == null) {
|
||||
const cp = input_translate.codepointFromButton(btn, mods);
|
||||
sendKey(2, cp, cp, mods);
|
||||
if (cp != 0) sendKey(2, cp, cp, mods);
|
||||
}
|
||||
},
|
||||
.button_release => |btn| {
|
||||
|
|
@ -241,14 +272,13 @@ fn wioLoop() void {
|
|||
if (input_translate.mouseButtonId(btn)) |mb_id| {
|
||||
const col: i32 = @intCast(mouse_pos.x);
|
||||
const row: i32 = @intCast(mouse_pos.y);
|
||||
const font = getFont();
|
||||
const col_cell: i32 = @intCast(@divTrunc(col, font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(row, font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(col, font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(row, font.cell_size.y));
|
||||
const col_cell: i32 = @intCast(@divTrunc(col, wio_font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(row, wio_font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(col, wio_font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(row, wio_font.cell_size.y));
|
||||
tui_pid.send(.{
|
||||
"RDR", "B",
|
||||
@as(u8, 0), // release
|
||||
@as(u8, 3), // release
|
||||
mb_id,
|
||||
col_cell,
|
||||
row_cell,
|
||||
|
|
@ -257,7 +287,7 @@ fn wioLoop() void {
|
|||
}) catch {};
|
||||
} else {
|
||||
const cp = input_translate.codepointFromButton(btn, mods);
|
||||
sendKey(3, cp, cp, mods);
|
||||
if (cp != 0) sendKey(3, cp, cp, mods);
|
||||
}
|
||||
},
|
||||
.char => |cp| {
|
||||
|
|
@ -266,11 +296,10 @@ fn wioLoop() void {
|
|||
},
|
||||
.mouse => |pos| {
|
||||
mouse_pos = pos;
|
||||
const font = getFont();
|
||||
const col_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(pos.x)), font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(pos.y)), font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(@as(i32, @intCast(pos.x)), font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(@as(i32, @intCast(pos.y)), font.cell_size.y));
|
||||
const col_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(pos.x)), wio_font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(pos.y)), wio_font.cell_size.y));
|
||||
const xoff: i32 = @intCast(@mod(@as(i32, @intCast(pos.x)), wio_font.cell_size.x));
|
||||
const yoff: i32 = @intCast(@mod(@as(i32, @intCast(pos.y)), wio_font.cell_size.y));
|
||||
tui_pid.send(.{
|
||||
"RDR", "M",
|
||||
col_cell, row_cell,
|
||||
|
|
@ -279,33 +308,39 @@ fn wioLoop() void {
|
|||
},
|
||||
.scroll_vertical => |dy| {
|
||||
const btn_id: u8 = if (dy < 0) 64 else 65; // up / down scroll
|
||||
const font = getFont();
|
||||
const col_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.x)), font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.y)), font.cell_size.y));
|
||||
const col_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.x)), wio_font.cell_size.x));
|
||||
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.y)), wio_font.cell_size.y));
|
||||
tui_pid.send(.{ "RDR", "B", @as(u8, 1), btn_id, col_cell, row_cell, @as(i32, 0), @as(i32, 0) }) catch {};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Paint if the tui pushed new screen data
|
||||
// Paint if the tui pushed new screen data.
|
||||
// Take ownership of the snap (set screen_snap = null under the mutex)
|
||||
// so the TUI thread cannot free the backing memory while we use it.
|
||||
if (screen_pending.swap(false, .acq_rel)) {
|
||||
screen_mutex.lock();
|
||||
const snap = screen_snap;
|
||||
screen_snap = null; // wio thread now owns this allocation
|
||||
screen_mutex.unlock();
|
||||
|
||||
if (snap) |s| {
|
||||
defer {
|
||||
allocator.free(s.cells);
|
||||
allocator.free(s.codepoints);
|
||||
}
|
||||
|
||||
state.size = .{ .x = win_size.width, .y = win_size.height };
|
||||
const font = s.font;
|
||||
const font = wio_font;
|
||||
|
||||
// Regenerate glyph indices using the GPU state
|
||||
const cells_with_glyphs = allocator.alloc(gpu.Cell, s.cells.len) catch continue;
|
||||
defer allocator.free(cells_with_glyphs);
|
||||
@memcpy(cells_with_glyphs, s.cells);
|
||||
|
||||
for (cells_with_glyphs) |*cell| {
|
||||
// TODO: carry codepoint/width from the vaxis screen snapshot.
|
||||
cell.glyph_index = state.generateGlyph(font, ' ', .single);
|
||||
for (cells_with_glyphs, s.codepoints) |*cell, cp| {
|
||||
cell.glyph_index = state.generateGlyph(font, cp, .single);
|
||||
}
|
||||
|
||||
gpu.paint(
|
||||
|
|
@ -332,9 +367,8 @@ fn sendResize(
|
|||
cell_width: *u16,
|
||||
cell_height: *u16,
|
||||
) void {
|
||||
const font = getFont();
|
||||
cell_width.* = @intCast(@divTrunc(sz.width, font.cell_size.x));
|
||||
cell_height.* = @intCast(@divTrunc(sz.height, font.cell_size.y));
|
||||
cell_width.* = @intCast(@divTrunc(sz.width, wio_font.cell_size.x));
|
||||
cell_height.* = @intCast(@divTrunc(sz.height, wio_font.cell_size.y));
|
||||
state.size = .{ .x = sz.width, .y = sz.height };
|
||||
tui_pid.send(.{
|
||||
"RDR", "Resize",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue