diff --git a/src/DevMode.zig b/src/DevMode.zig index 08cca5d4e..4089076dd 100644 --- a/src/DevMode.zig +++ b/src/DevMode.zig @@ -45,13 +45,13 @@ pub fn update(self: *DevMode) !void { if (imgui.treeNode("Atlas: Greyscale", .{ .default_open = true })) { defer imgui.treePop(); const atlas = &window.font_group.atlas_greyscale; - try self.atlasInfo(atlas, @intCast(usize, window.grid.texture.id)); + try self.atlasInfo(atlas, @intCast(usize, window.renderer.texture.id)); } if (imgui.treeNode("Atlas: Color (Emoji)", .{ .default_open = true })) { defer imgui.treePop(); const atlas = &window.font_group.atlas_color; - try self.atlasInfo(atlas, @intCast(usize, window.grid.texture_color.id)); + try self.atlasInfo(atlas, @intCast(usize, window.renderer.texture_color.id)); } } } diff --git a/src/Window.zig b/src/Window.zig index d8a9fdcf2..dd1485e5d 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -9,7 +9,7 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Grid = @import("Grid.zig"); +const renderer = @import("renderer.zig"); const glfw = @import("glfw"); const gl = @import("opengl.zig"); const imgui = @import("imgui"); @@ -53,8 +53,8 @@ imgui_ctx: if (DevMode.enabled) *imgui.Context else void, /// Whether the window is currently focused focused: bool, -/// The terminal grid attached to this window. -grid: Grid, +/// The renderer for this window. +renderer: renderer.OpenGL, /// The underlying pty for this window. pty: Pty, @@ -368,14 +368,14 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo // Create our terminal grid with the initial window size const window_size = try window.getSize(); - var grid = try Grid.init(alloc, font_group); - try grid.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); - grid.background = .{ + var renderer_impl = try renderer.OpenGL.init(alloc, font_group); + try renderer_impl.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); + renderer_impl.background = .{ .r = config.background.r, .g = config.background.g, .b = config.background.b, }; - grid.foreground = .{ + renderer_impl.foreground = .{ .r = config.foreground.r, .g = config.foreground.g, .b = config.foreground.b, @@ -384,14 +384,14 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app // but is otherwise somewhat arbitrary. try window.setSizeLimits(.{ - .width = @floatToInt(u32, grid.cell_size.width * 10), - .height = @floatToInt(u32, grid.cell_size.height * 4), + .width = @floatToInt(u32, renderer_impl.cell_size.width * 10), + .height = @floatToInt(u32, renderer_impl.cell_size.height * 4), }, .{ .width = null, .height = null }); // Create our pty var pty = try Pty.open(.{ - .ws_row = @intCast(u16, grid.size.rows), - .ws_col = @intCast(u16, grid.size.columns), + .ws_row = @intCast(u16, renderer_impl.size.rows), + .ws_col = @intCast(u16, renderer_impl.size.columns), .ws_xpixel = @intCast(u16, window_size.width), .ws_ypixel = @intCast(u16, window_size.height), }); @@ -434,7 +434,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo try stream.readStart(ttyReadAlloc, ttyRead); // Create our terminal - var term = try terminal.Terminal.init(alloc, grid.size.columns, grid.size.rows); + var term = try terminal.Terminal.init(alloc, renderer_impl.size.columns, renderer_impl.size.rows); errdefer term.deinit(alloc); // Setup a timer for blinking the cursor @@ -463,7 +463,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo .window = window, .cursor = cursor, .focused = false, - .grid = grid, + .renderer = renderer_impl, .pty = pty, .command = cmd, .mouse = .{}, @@ -556,7 +556,7 @@ pub fn destroy(self: *Window) void { log.err("error waiting for command to exit: {}", .{err}); self.terminal.deinit(self.alloc); - self.grid.deinit(); + self.renderer.deinit(); self.window.destroy(); self.terminal_cursor.timer.close((struct { @@ -662,19 +662,19 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { // Update our grid so that the projections on render are correct. const win = window.getUserPointer(Window) orelse return; - win.grid.setScreenSize(.{ + win.renderer.setScreenSize(.{ .width = px_size.width, .height = px_size.height, }) catch |err| log.err("error updating grid screen size err={}", .{err}); // Update the size of our terminal state - win.terminal.resize(win.alloc, win.grid.size.columns, win.grid.size.rows) catch |err| + win.terminal.resize(win.alloc, win.renderer.size.columns, win.renderer.size.rows) catch |err| log.err("error updating terminal size: {}", .{err}); // Update the size of our pty win.pty.setSize(.{ - .ws_row = @intCast(u16, win.grid.size.rows), - .ws_col = @intCast(u16, win.grid.size.columns), + .ws_row = @intCast(u16, win.renderer.size.rows), + .ws_col = @intCast(u16, win.renderer.size.columns), .ws_xpixel = @intCast(u16, width), .ws_ypixel = @intCast(u16, height), }) catch |err| log.err("error updating pty screen size err={}", .{err}); @@ -1007,7 +1007,7 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { // Positive is up const sign: isize = if (yoff > 0) -1 else 1; - const delta: isize = sign * @max(@divFloor(win.grid.size.rows, 15), 1); + const delta: isize = sign * @max(@divFloor(win.renderer.size.rows, 15), 1); log.info("scroll: delta={}", .{delta}); win.terminal.scrollViewport(.{ .delta = delta }) catch |err| log.err("error scrolling viewport err={}", .{err}); @@ -1367,10 +1367,10 @@ fn cursorPosCallback( // // the boundary point at which we consider selection or non-selection - const cell_xboundary = win.grid.cell_size.width * 0.6; + const cell_xboundary = win.renderer.cell_size.width * 0.6; // first xpos of the clicked cell - const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.grid.cell_size.width; + const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.renderer.cell_size.width; const cell_start_xpos = win.mouse.left_click_xpos - cell_xstart; // If this is the same cell, then we only start the selection if weve @@ -1445,7 +1445,7 @@ fn posToViewport(self: Window, xpos: f64, ypos: f64) terminal.point.Viewport { return .{ .x = if (xpos < 0) 0 else x: { // Our cell is the mouse divided by cell width - const cell_width = @floatCast(f64, self.grid.cell_size.width); + const cell_width = @floatCast(f64, self.renderer.cell_size.width); const x = @floatToInt(usize, xpos / cell_width); // Can be off the screen if the user drags it out, so max @@ -1454,7 +1454,7 @@ fn posToViewport(self: Window, xpos: f64, ypos: f64) terminal.point.Viewport { }, .y = if (ypos < 0) 0 else y: { - const cell_height = @floatCast(f64, self.grid.cell_size.height); + const cell_height = @floatCast(f64, self.renderer.cell_size.height); const y = @floatToInt(usize, ypos / cell_height); break :y @min(y, self.terminal.rows - 1); }, @@ -1584,23 +1584,23 @@ fn renderTimerCallback(t: *libuv.Timer) void { // Setup our cursor settings if (win.focused) { - win.grid.cursor_visible = win.terminal_cursor.visible and !win.terminal_cursor.blink; - win.grid.cursor_style = Grid.CursorStyle.fromTerminal(win.terminal_cursor.style) orelse .box; + win.renderer.cursor_visible = win.terminal_cursor.visible and !win.terminal_cursor.blink; + win.renderer.cursor_style = renderer.OpenGL.CursorStyle.fromTerminal(win.terminal_cursor.style) orelse .box; } else { - win.grid.cursor_visible = true; - win.grid.cursor_style = .box_hollow; + win.renderer.cursor_visible = true; + win.renderer.cursor_style = .box_hollow; } // Calculate foreground and background colors - const bg = win.grid.background; - const fg = win.grid.foreground; + const bg = win.renderer.background; + const fg = win.renderer.foreground; defer { - win.grid.background = bg; - win.grid.foreground = fg; + win.renderer.background = bg; + win.renderer.foreground = fg; } if (win.terminal.modes.reverse_colors) { - win.grid.background = fg; - win.grid.foreground = bg; + win.renderer.background = fg; + win.renderer.foreground = bg; } // Set our background @@ -1624,15 +1624,15 @@ fn renderTimerCallback(t: *libuv.Timer) void { gl.clear(gl.c.GL_COLOR_BUFFER_BIT); // For now, rebuild all cells - win.grid.rebuildCells(&win.terminal) catch |err| + win.renderer.rebuildCells(&win.terminal) catch |err| log.err("error calling rebuildCells in render timer err={}", .{err}); // Finalize the cells prior to render - win.grid.finalizeCells(&win.terminal) catch |err| + win.renderer.finalizeCells(&win.terminal) catch |err| log.err("error calling updateCells in render timer err={}", .{err}); // Render the grid - win.grid.render() catch |err| { + win.renderer.render() catch |err| { log.err("error rendering grid: {}", .{err}); return; }; @@ -1812,7 +1812,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { self.terminal.setDeccolmSupported(enabled); // Force resize back to the window size - self.terminal.resize(self.alloc, self.grid.size.columns, self.grid.size.rows) catch |err| + self.terminal.resize(self.alloc, self.renderer.size.columns, self.renderer.size.rows) catch |err| log.err("error updating terminal size: {}", .{err}); }, diff --git a/src/main.zig b/src/main.zig index b543215c7..7371fb811 100644 --- a/src/main.zig +++ b/src/main.zig @@ -105,16 +105,15 @@ fn glfwErrorCallback(code: glfw.Error, desc: [:0]const u8) void { test { _ = @import("Atlas.zig"); - _ = @import("Grid.zig"); _ = @import("Pty.zig"); _ = @import("Command.zig"); _ = @import("TempDir.zig"); _ = @import("font/main.zig"); + _ = @import("renderer.zig"); _ = @import("terminal/Terminal.zig"); _ = @import("input.zig"); // Libraries - _ = @import("libuv"); _ = @import("segmented_pool.zig"); _ = @import("terminal/main.zig"); diff --git a/src/renderer.zig b/src/renderer.zig new file mode 100644 index 000000000..d55b2565f --- /dev/null +++ b/src/renderer.zig @@ -0,0 +1,14 @@ +//! Renderer implementation and utilities. The renderer is responsible for +//! taking the internal screen state and turning into some output format, +//! usually for a screen. +//! +//! The renderer is closely tied to the windowing system which usually +//! has to prepare the window for the given renderer using system-specific +//! APIs. The renderers in this package assume that the renderer is already +//! setup (OpenGL has a context, Vulkan has a surface, etc.) + +pub const OpenGL = @import("renderer/OpenGL.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/Grid.zig b/src/renderer/OpenGL.zig similarity index 97% rename from src/Grid.zig rename to src/renderer/OpenGL.zig index b870d9dda..e56f832a0 100644 --- a/src/Grid.zig +++ b/src/renderer/OpenGL.zig @@ -1,18 +1,18 @@ -//! Represents a single terminal grid. -const Grid = @This(); +//! Rendering implementation for OpenGL. +pub const OpenGL = @This(); const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; const Allocator = std.mem.Allocator; -const Atlas = @import("Atlas.zig"); -const font = @import("font/main.zig"); -const terminal = @import("terminal/main.zig"); +const Atlas = @import("../Atlas.zig"); +const font = @import("../font/main.zig"); +const terminal = @import("../terminal/main.zig"); const Terminal = terminal.Terminal; -const gl = @import("opengl.zig"); +const gl = @import("../opengl.zig"); const trace = @import("tracy").trace; -const math = @import("math.zig"); -const lru = @import("lru.zig"); +const math = @import("../math.zig"); +const lru = @import("../lru.zig"); const log = std.log.scoped(.grid); @@ -152,7 +152,7 @@ const GPUCellMode = enum(u8) { } }; -pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Grid { +pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL { // Create the initial font shaper var shape_buf = try alloc.alloc(font.Shaper.Cell, 1); errdefer alloc.free(shape_buf); @@ -171,8 +171,8 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Grid { // Create our shader const program = try gl.Program.createVF( - @embedFile("./shaders/cell.v.glsl"), - @embedFile("./shaders/cell.f.glsl"), + @embedFile("../shaders/cell.v.glsl"), + @embedFile("../shaders/cell.f.glsl"), ); // Set our cell dimensions @@ -284,7 +284,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Grid { ); } - return Grid{ + return OpenGL{ .alloc = alloc, .cells = .{}, .cells_lru = CellsLRU.init(0), @@ -305,7 +305,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Grid { }; } -pub fn deinit(self: *Grid) void { +pub fn deinit(self: *OpenGL) void { self.font_shaper.deinit(); self.alloc.free(self.font_shaper.cell_buf); @@ -327,7 +327,7 @@ pub fn deinit(self: *Grid) void { /// /// Note this doesn't have to typically be manually called. Internally, /// the renderer will do this when it needs more memory space. -pub fn rebuildCells(self: *Grid, term: *Terminal) !void { +pub fn rebuildCells(self: *OpenGL, term: *Terminal) !void { const t = trace(@src()); defer t.end(); @@ -456,7 +456,7 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void { /// This should be called prior to render to finalize the cells and prepare /// for render. This performs tasks such as preparing the cursor, refreshing /// the cells if necessary, etc. -pub fn finalizeCells(self: *Grid, term: *Terminal) !void { +pub fn finalizeCells(self: *OpenGL, term: *Terminal) !void { // If we're out of space or we have no more Z-space, rebuild. if (self.cells.items.len == self.cells.capacity) { log.info("cell cache full, rebuilding from scratch", .{}); @@ -468,7 +468,7 @@ pub fn finalizeCells(self: *Grid, term: *Terminal) !void { try self.flushAtlas(); } -fn addCursor(self: *Grid, term: *Terminal) void { +fn addCursor(self: *OpenGL, term: *Terminal) void { // Add the cursor if (self.cursor_visible and term.screen.viewportIsBottom()) { const cell = term.screen.getCell( @@ -503,7 +503,7 @@ fn addCursor(self: *Grid, term: *Terminal) void { /// or not. If the cell wasn't updated, a full refreshCells call is /// needed. pub fn updateCell( - self: *Grid, + self: *OpenGL, term: *Terminal, cell: terminal.Screen.Cell, shaper_cell: font.Shaper.Cell, @@ -690,7 +690,7 @@ pub fn updateCell( /// Set the screen size for rendering. This will update the projection /// used for the shader so that the scaling of the grid is correct. -pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void { +pub fn setScreenSize(self: *OpenGL, dim: ScreenSize) !void { // Update the projection uniform within our shader const bind = try self.program.use(); defer bind.unbind(); @@ -725,7 +725,7 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void { } /// Updates the font texture atlas if it is dirty. -fn flushAtlas(self: *Grid) !void { +fn flushAtlas(self: *OpenGL) !void { { const atlas = &self.font_group.atlas_greyscale; if (atlas.modified) { @@ -797,7 +797,7 @@ fn flushAtlas(self: *Grid) !void { /// Render renders the current cell state. This will not modify any of /// the cells. -pub fn render(self: *Grid) !void { +pub fn render(self: *OpenGL) !void { const t = trace(@src()); defer t.end();