diff --git a/build.zig b/build.zig index 8d3c126e..bc78781f 100644 --- a/build.zig +++ b/build.zig @@ -3,6 +3,8 @@ const builtin = @import("builtin"); const optimize_deps = .ReleaseFast; +pub const Renderer = enum { terminal, gui, d3d11 }; + pub fn build(b: *std.Build) void { const all_targets = b.option(bool, "all_targets", "Build all known good targets during release builds (default: no)") orelse false; const tracy_enabled = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)") orelse false; @@ -10,7 +12,7 @@ pub fn build(b: *std.Build) void { const strip = b.option(bool, "strip", "Disable debug information (default: no)"); const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)"); const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)"); - const gui = b.option(bool, "gui", "Standalone GUI mode") orelse false; + const renderer = b.option(Renderer, "renderer", "Renderer backend: terminal (TUI, default), d3d11 (GPU on Windows via DirectWrite), gui (GPU on Linux/macOS/Windows via wio+sokol_gfx)") orelse .terminal; const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; const run_step = b.step("run", "Run the app"); @@ -47,7 +49,7 @@ pub fn build(b: *std.Build) void { strip, use_llvm, pie, - gui, + renderer, version.items, all_targets, test_filters, @@ -65,7 +67,7 @@ fn build_development( strip: ?bool, use_llvm: ?bool, pie: ?bool, - gui: bool, + renderer: Renderer, version: []const u8, _: bool, // all_targets test_filters: []const []const u8, @@ -87,7 +89,7 @@ fn build_development( strip orelse false, use_llvm, pie, - gui, + renderer, version, test_filters, ); @@ -104,7 +106,7 @@ fn build_release( _: ?bool, //release builds control strip use_llvm: ?bool, pie: ?bool, - _: bool, //gui + _: Renderer, //renderer version: []const u8, all_targets: bool, test_filters: []const []const u8, @@ -171,7 +173,7 @@ fn build_release( true, // strip release builds use_llvm, pie, - false, //gui + .terminal, version, test_filters, ); @@ -190,7 +192,7 @@ fn build_release( false, // don't strip debug builds use_llvm, pie, - false, //gui + .terminal, version, test_filters, ); @@ -210,7 +212,7 @@ fn build_release( true, // strip release builds use_llvm, pie, - true, //gui + .d3d11, version, test_filters, ); @@ -229,7 +231,7 @@ fn build_release( false, // don't strip debug builds use_llvm, pie, - true, //gui + .d3d11, version, test_filters, ); @@ -251,7 +253,7 @@ pub fn build_exe( strip: bool, use_llvm: ?bool, pie: ?bool, - gui: bool, + renderer: Renderer, version: []const u8, test_filters: []const []const u8, ) void { @@ -259,7 +261,7 @@ pub fn build_exe( options.addOption(bool, "enable_tracy", tracy_enabled); options.addOption(bool, "use_tree_sitter", use_tree_sitter); options.addOption(bool, "strip", strip); - options.addOption(bool, "gui", gui); + options.addOption(bool, "gui", renderer != .terminal); const options_mod = options.createModule(); @@ -489,8 +491,9 @@ pub fn build_exe( }); const renderer_mod = blk: { - if (gui) switch (target.result.os.tag) { - .windows => { + switch (renderer) { + .terminal => break :blk tui_renderer_mod, + .d3d11 => { const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod; const win32_mod = win32_dep.module("win32"); const gui_mod = b.createModule(.{ @@ -526,12 +529,11 @@ pub fn build_exe( }); break :blk mod; }, - else => |tag| { - std.log.err("OS '{t}' does not support -Dgui mode", .{tag}); + .gui => { + std.log.err("-Drenderer=gui is not yet implemented", .{}); std.process.exit(0xff); }, - }; - break :blk tui_renderer_mod; + } }; const keybind_mod = b.createModule(.{ @@ -681,7 +683,7 @@ pub fn build_exe( }, }); - const exe_name = if (gui) "flow-gui" else "flow"; + const exe_name = if (renderer != .terminal) "flow-gui" else "flow"; const exe = b.addExecutable(.{ .name = exe_name, @@ -727,7 +729,7 @@ pub fn build_exe( exe.addWin32ResourceFile(.{ .file = b.path("src/win32/flow.rc"), }); - if (gui) { + if (renderer != .terminal) { exe.subsystem = .Windows; } } diff --git a/src/gui/Cell.zig b/src/gui/Cell.zig new file mode 100644 index 00000000..242d534a --- /dev/null +++ b/src/gui/Cell.zig @@ -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, +}; diff --git a/src/win32/GlyphIndexCache.zig b/src/gui/GlyphIndexCache.zig similarity index 100% rename from src/win32/GlyphIndexCache.zig rename to src/gui/GlyphIndexCache.zig diff --git a/src/gui/GlyphRasterizer.zig b/src/gui/GlyphRasterizer.zig new file mode 100644 index 00000000..0dc2ca7b --- /dev/null +++ b/src/gui/GlyphRasterizer.zig @@ -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 = ; // font handle (scale, metrics, etc.) +// pub const Fonts = ; // 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"); +} diff --git a/src/win32/xterm.zig b/src/gui/xterm.zig similarity index 100% rename from src/win32/xterm.zig rename to src/gui/xterm.zig diff --git a/src/win32/xy.zig b/src/gui/xy.zig similarity index 100% rename from src/win32/xy.zig rename to src/gui/xy.zig diff --git a/src/win32/DwriteRenderer.zig b/src/win32/DwriteRenderer.zig index 0a9b20df..003a2949 100644 --- a/src/win32/DwriteRenderer.zig +++ b/src/win32/DwriteRenderer.zig @@ -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; diff --git a/src/win32/d3d11.zig b/src/win32/d3d11.zig index 938fd3c1..d7cd4359 100644 --- a/src/win32/d3d11.zig +++ b/src/win32/d3d11.zig @@ -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); diff --git a/src/win32/gui.zig b/src/win32/gui.zig index e770d7f4..6b8c4efb 100644 --- a/src/win32/gui.zig +++ b/src/win32/gui.zig @@ -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;