feat(gui): M1 shared core — extract src/gui/ and replace -Dgui with -Drenderer

Move shared types out of src/win32/ into a new src/gui/ package:
- xy.zig: generic XY(T) coordinate type
- xterm.zig: 256-color palette table (used by all GPU renderers)
- GlyphIndexCache.zig: glyph→atlas-slot mapping
- Cell.zig: Rgba8 and Cell extracted from d3d11.zig
- GlyphRasterizer.zig: comptime interface spec/checker

Update src/win32/ imports to point to ../gui/ and delete the
moved originals.

Replace the -Dgui bool option with -Drenderer enum { terminal, gui,
d3d11 }, wiring .d3d11 to the existing win32 DirectWrite path and
keeping build_options.gui = (renderer != .terminal) for tui.zig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
CJ van den Berg 2026-03-28 23:22:31 +01:00
parent a782bfb690
commit 9c66b19650
9 changed files with 101 additions and 44 deletions

20
src/gui/Cell.zig Normal file
View file

@ -0,0 +1,20 @@
// Shared GPU cell types used by all GPU renderer backends.
pub const Rgba8 = packed struct(u32) {
a: u8,
b: u8,
g: u8,
r: u8,
pub fn initRgb(r: u8, g: u8, b: u8) Rgba8 {
return .{ .r = r, .g = g, .b = b, .a = 255 };
}
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Rgba8 {
return .{ .r = r, .g = g, .b = b, .a = a };
}
};
pub const Cell = extern struct {
glyph_index: u32,
background: Rgba8,
foreground: Rgba8,
};

View file

@ -0,0 +1,50 @@
// GlyphRasterizer comptime interface specification.
//
// Zig's duck-typing means no vtable is needed. This file documents the
// interface that every rasterizer implementation must satisfy and can be
// used as a comptime checker.
//
// A rasterizer implementation must provide:
//
// pub const Font = <type>; // font handle (scale, metrics, etc.)
// pub const Fonts = <type>; // font enumeration (for UI font picker)
//
// pub fn init(allocator: std.mem.Allocator) !Self
// pub fn deinit(self: *Self) void
//
// pub fn loadFont(self: *Self, name: []const u8, size_px: u16) !Font
// Load a named font at a given pixel height. The returned Font
// contains pre-computed metrics (cell_size, scale, ascent_px).
//
// pub fn render(
// self: *const Self,
// font: Font,
// codepoint: u21,
// kind: enum { single, left, right },
// staging_buf: []u8,
// ) void
// Rasterize a single glyph into the caller-provided A8 staging buffer.
// - staging_buf is zero-filled by the caller before each call.
// - For `single`: buffer is cell_size.x * cell_size.y bytes.
// - For `left`/`right`: buffer is 2*cell_size.x * cell_size.y bytes.
// `left` rasterizes at double width into the full buffer.
// `right` places the glyph offset by cell_size.x so that the right
// half of the buffer contains the right portion of the glyph.
const std = @import("std");
pub fn check(comptime Rasterizer: type) void {
const has_Font = @hasDecl(Rasterizer, "Font");
const has_Fonts = @hasDecl(Rasterizer, "Fonts");
const has_init = @hasDecl(Rasterizer, "init");
const has_deinit = @hasDecl(Rasterizer, "deinit");
const has_loadFont = @hasDecl(Rasterizer, "loadFont");
const has_render = @hasDecl(Rasterizer, "render");
if (!has_Font) @compileError("Rasterizer missing: pub const Font");
if (!has_Fonts) @compileError("Rasterizer missing: pub const Fonts");
if (!has_init) @compileError("Rasterizer missing: pub fn init");
if (!has_deinit) @compileError("Rasterizer missing: pub fn deinit");
if (!has_loadFont) @compileError("Rasterizer missing: pub fn loadFont");
if (!has_render) @compileError("Rasterizer missing: pub fn render");
}

View file

@ -5,7 +5,7 @@ const win32 = @import("win32").everything;
const win32ext = @import("win32ext.zig");
const dwrite = @import("dwrite.zig");
const XY = @import("xy.zig").XY;
const XY = @import("../gui/xy.zig").XY;
pub const Font = dwrite.Font;
pub const Fonts = dwrite.Fonts;

View file

@ -4,10 +4,11 @@ const win32 = @import("win32").everything;
const win32ext = @import("win32ext.zig");
const dwrite = @import("dwrite.zig");
const GlyphIndexCache = @import("GlyphIndexCache.zig");
const GlyphIndexCache = @import("../gui/GlyphIndexCache.zig");
const TextRenderer = @import("DwriteRenderer.zig");
const XY = @import("xy.zig").XY;
const XY = @import("../gui/xy.zig").XY;
const gui_cell = @import("../gui/Cell.zig");
pub const Font = TextRenderer.Font;
pub const Fonts = TextRenderer.Fonts;
@ -31,21 +32,9 @@ const global = struct {
var background: Rgba8 = .{ .r = 19, .g = 19, .b = 19, .a = 255 };
};
pub const Color = Rgba8;
const Rgba8 = packed struct(u32) {
a: u8,
b: u8,
g: u8,
r: u8,
pub fn initRgb(r: u8, g: u8, b: u8) Color {
return .{ .r = r, .g = g, .b = b, .a = 255 };
}
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Color {
return .{ .r = r, .g = g, .b = b, .a = a };
}
};
pub const Cell = shader.Cell;
pub const Color = gui_cell.Rgba8;
const Rgba8 = gui_cell.Rgba8;
pub const Cell = gui_cell.Cell;
// types shared with the shader
pub const shader = struct {
@ -54,11 +43,7 @@ pub const shader = struct {
col_count: u32,
row_count: u32,
};
pub const Cell = extern struct {
glyph_index: u32,
background: Rgba8,
foreground: Rgba8,
};
pub const Cell = gui_cell.Cell;
};
const swap_chain_flags: u32 = @intFromEnum(win32.DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT);

View file

@ -18,10 +18,10 @@ const input = @import("input");
const windowmsg = @import("windowmsg.zig");
const render = @import("d3d11.zig");
const xterm = @import("xterm.zig");
const xterm = @import("../gui/xterm.zig");
const FontFace = @import("FontFace.zig");
const XY = @import("xy.zig").XY;
const XY = @import("../gui/xy.zig").XY;
const WM_APP_EXIT = win32.WM_APP + 1;
const WM_APP_SET_BACKGROUND = win32.WM_APP + 2;