renderer/metal: remove old functions

This commit is contained in:
Mitchell Hashimoto
2024-04-28 09:41:45 -07:00
parent f7714a113d
commit a299338099

View File

@ -778,17 +778,8 @@ pub fn updateFrame(
if (critical.preedit) |p| p.deinit(self.alloc);
}
// Build our GPU cells (OLD)
// try self.rebuildCells(
// &critical.screen,
// critical.mouse,
// critical.preedit,
// critical.cursor_style,
// &critical.color_palette,
// );
// Build our GPU cells
try self.rebuildCells2(
try self.rebuildCells(
&critical.screen,
critical.mouse,
critical.preedit,
@ -1645,227 +1636,10 @@ pub fn setScreenSize(
log.debug("screen size screen={} grid={}, cell_width={} cell_height={}", .{ dim, grid_size, self.grid_metrics.cell_width, self.grid_metrics.cell_height });
}
/// Sync all the CPU cells with the GPU state (but still on the CPU here).
/// This builds all our "GPUCells" on this struct, but doesn't send them
/// down to the GPU yet.
fn rebuildCells(
self: *Metal,
screen: *terminal.Screen,
mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit,
cursor_style_: ?renderer.CursorStyle,
color_palette: *const terminal.color.Palette,
) !void {
const rows_usize: usize = @intCast(screen.pages.rows);
const cols_usize: usize = @intCast(screen.pages.cols);
// Bg cells at most will need space for the visible screen size
self.cells_bg.clearRetainingCapacity();
try self.cells_bg.ensureTotalCapacity(
self.alloc,
rows_usize * cols_usize,
);
// Over-allocate just to ensure we don't allocate again during loops.
self.cells_text.clearRetainingCapacity();
try self.cells_text.ensureTotalCapacity(
self.alloc,
// * 3 for glyph + underline + strikethrough for each cell
// + 1 for cursor
(rows_usize * cols_usize * 3) + 1,
);
// Create an arena for all our temporary allocations while rebuilding
var arena = ArenaAllocator.init(self.alloc);
defer arena.deinit();
const arena_alloc = arena.allocator();
// Create our match set for the links.
var link_match_set: link.MatchSet = if (mouse.point) |mouse_pt| try self.config.links.matchSet(
arena_alloc,
screen,
mouse_pt,
mouse.mods,
) else .{};
// Determine our x/y range for preedit. We don't want to render anything
// here because we will render the preedit separately.
const preedit_range: ?struct {
y: terminal.size.CellCountInt,
x: [2]terminal.size.CellCountInt,
cp_offset: usize,
} = if (preedit) |preedit_v| preedit: {
const range = preedit_v.range(screen.cursor.x, screen.pages.cols - 1);
break :preedit .{
.y = screen.cursor.y,
.x = .{ range.start, range.end },
.cp_offset = range.cp_offset,
};
} else null;
// This is the cell that has [mode == .fg] and is underneath our cursor.
// We keep track of it so that we can invert the colors so the character
// remains visible.
var cursor_cell: ?mtl_shaders.CellText = null;
// Build each cell
var row_it = screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
var y: terminal.size.CellCountInt = 0;
while (row_it.next()) |row| {
defer y += 1;
// True if this is the row with our cursor. There are a lot of conditions
// here because the reasons we need to know this are primarily to invert.
//
// - If we aren't drawing the cursor then we don't need to change our rendering.
// - If the cursor is not visible, then we don't need to change rendering.
// - If the cursor style is not a box, then we don't need to change
// rendering because it'll never fully overlap a glyph.
// - If the viewport is not at the bottom, then we don't need to
// change rendering because the cursor is not visible.
// (NOTE: this may not be fully correct, we may be scrolled
// slightly up and the cursor may be visible)
// - If this y doesn't match our cursor y then we don't need to
// change rendering.
//
const cursor_row = if (cursor_style_) |cursor_style|
cursor_style == .block and
screen.viewportIsBottom() and
y == screen.cursor.y
else
false;
// True if we want to do font shaping around the cursor. We want to
// do font shaping as long as the cursor is enabled.
const shape_cursor = screen.viewportIsBottom() and
y == screen.cursor.y;
// If this is the row with our cursor, then we may have to modify
// the cell with the cursor.
const start_i: usize = self.cells_text.items.len;
defer if (cursor_row) {
// If we're on a wide spacer tail, then we want to look for
// the previous cell.
const screen_cell = row.cells(.all)[screen.cursor.x];
const x = screen.cursor.x - @intFromBool(screen_cell.wide == .spacer_tail);
for (self.cells_text.items[start_i..]) |cell| {
if (cell.grid_pos[0] == x and
(cell.mode == .fg or cell.mode == .fg_color))
{
cursor_cell = cell;
break;
}
}
};
// We need to get this row's selection if there is one for proper
// run splitting.
const row_selection = sel: {
const sel = screen.selection orelse break :sel null;
const pin = screen.pages.pin(.{ .viewport = .{ .y = y } }) orelse
break :sel null;
break :sel sel.containedRow(screen, pin) orelse null;
};
// Split our row into runs and shape each one.
var iter = self.font_shaper.runIterator(
self.font_grid,
screen,
row,
row_selection,
if (shape_cursor) screen.cursor.x else null,
);
while (try iter.next(self.alloc)) |run| {
for (try self.font_shaper.shape(run)) |shaper_cell| {
// If this cell falls within our preedit range then we skip it.
// We do this so we don't have conflicting data on the same
// cell.
if (preedit_range) |range| {
if (range.y == y and
shaper_cell.x >= range.x[0] and
shaper_cell.x <= range.x[1])
{
continue;
}
}
// It this cell is within our hint range then we need to
// underline it.
const cell: terminal.Pin = cell: {
var copy = row;
copy.x = shaper_cell.x;
break :cell copy;
};
if (self.updateCell(
screen,
cell,
if (link_match_set.orderedContains(screen, cell))
.single
else
null,
color_palette,
shaper_cell,
run,
shaper_cell.x,
y,
)) |update| {
assert(update);
} else |err| {
log.warn("error building cell, will be invalid x={} y={}, err={}", .{
shaper_cell.x,
y,
err,
});
}
}
}
}
// Add the cursor at the end so that it overlays everything. If we have
// a cursor cell then we invert the colors on that and add it in so
// that we can always see it.
if (cursor_style_) |cursor_style| cursor_style: {
// If we have a preedit, we try to render the preedit text on top
// of the cursor.
if (preedit) |preedit_v| {
const range = preedit_range.?;
var x = range.x[0];
for (preedit_v.codepoints[range.cp_offset..]) |cp| {
self.addPreeditCell(cp, x, range.y) catch |err| {
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
x,
range.y,
err,
});
};
x += if (cp.wide) 2 else 1;
}
// Preedit hides the cursor
break :cursor_style;
}
_ = self.addCursor(screen, cursor_style);
if (cursor_cell) |*cell| {
if (cell.mode == .fg) {
cell.color = if (self.config.cursor_text) |txt|
.{ txt.r, txt.g, txt.b, 255 }
else
.{ self.background_color.r, self.background_color.g, self.background_color.b, 255 };
}
self.cells_text.appendAssumeCapacity(cell.*);
}
}
}
/// Convert the terminal state to GPU cells stored in CPU memory. These
/// are then synced to the GPU in the next frame. This only updates CPU
/// memory and doesn't touch the GPU.
fn rebuildCells2(
fn rebuildCells(
self: *Metal,
screen: *terminal.Screen,
mouse: renderer.State.Mouse,
@ -1960,7 +1734,7 @@ fn rebuildCells2(
break :cell copy;
};
if (self.updateCell2(
if (self.updateCell(
screen,
cell,
if (link_match_set.orderedContains(screen, cell))
@ -1998,7 +1772,7 @@ fn rebuildCells2(
// Prepare the cursor cell contents.
const style = cursor_style_ orelse break :cursor;
self.addCursor2(screen, style);
self.addCursor(screen, style);
// If the cursor is visible then we set our uniforms.
if (style == .block and screen.viewportIsBottom()) {
@ -2025,7 +1799,7 @@ fn rebuildCells2(
const range = preedit_range.?;
var x = range.x[0];
for (preedit_v.codepoints[range.cp_offset..]) |cp| {
self.addPreeditCell2(cp, .{ .x = x, .y = range.y }) catch |err| {
self.addPreeditCell(cp, .{ .x = x, .y = range.y }) catch |err| {
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
x,
range.y,
@ -2038,7 +1812,7 @@ fn rebuildCells2(
}
}
fn updateCell2(
fn updateCell(
self: *Metal,
screen: *const terminal.Screen,
cell_pin: terminal.Pin,
@ -2257,227 +2031,7 @@ fn updateCell2(
return true;
}
fn updateCell(
self: *Metal,
screen: *const terminal.Screen,
cell_pin: terminal.Pin,
cell_underline: ?terminal.Attribute.Underline,
palette: *const terminal.color.Palette,
shaper_cell: font.shape.Cell,
shaper_run: font.shape.TextRun,
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
) !bool {
const BgFg = struct {
/// Background is optional because in un-inverted mode
/// it may just be equivalent to the default background in
/// which case we do nothing to save on GPU render time.
bg: ?terminal.color.RGB,
/// Fg is always set to some color, though we may not render
/// any fg if the cell is empty or has no attributes like
/// underline.
fg: terminal.color.RGB,
};
// True if this cell is selected
const selected: bool = if (screen.selection) |sel|
sel.contains(screen, cell_pin)
else
false;
const rac = cell_pin.rowAndCell();
const cell = rac.cell;
const style = cell_pin.style(cell);
const underline = cell_underline orelse style.flags.underline;
// The colors for the cell.
const colors: BgFg = colors: {
// The normal cell result
const cell_res: BgFg = if (!style.flags.inverse) .{
// In normal mode, background and fg match the cell. We
// un-optionalize the fg by defaulting to our fg color.
.bg = style.bg(cell, palette),
.fg = style.fg(palette) orelse self.foreground_color,
} else .{
// In inverted mode, the background MUST be set to something
// (is never null) so it is either the fg or default fg. The
// fg is either the bg or default background.
.bg = style.fg(palette) orelse self.foreground_color,
.fg = style.bg(cell, palette) orelse self.background_color,
};
// If we are selected, we our colors are just inverted fg/bg
const selection_res: ?BgFg = if (selected) .{
.bg = if (self.config.invert_selection_fg_bg)
cell_res.fg
else
self.config.selection_background orelse self.foreground_color,
.fg = if (self.config.invert_selection_fg_bg)
cell_res.bg orelse self.background_color
else
self.config.selection_foreground orelse self.background_color,
} else null;
// If the cell is "invisible" then we just make fg = bg so that
// the cell is transparent but still copy-able.
const res: BgFg = selection_res orelse cell_res;
if (style.flags.invisible) {
break :colors BgFg{
.bg = res.bg,
.fg = res.bg orelse self.background_color,
};
}
break :colors res;
};
// Alpha multiplier
const alpha: u8 = if (style.flags.faint) 175 else 255;
// If the cell has a background, we always draw it.
const bg: [4]u8 = if (colors.bg) |rgb| bg: {
// Determine our background alpha. If we have transparency configured
// then this is dynamic depending on some situations. This is all
// in an attempt to make transparency look the best for various
// situations. See inline comments.
const bg_alpha: u8 = bg_alpha: {
const default: u8 = 255;
if (self.config.background_opacity >= 1) break :bg_alpha default;
// If we're selected, we do not apply background opacity
if (selected) break :bg_alpha default;
// If we're reversed, do not apply background opacity
if (style.flags.inverse) break :bg_alpha default;
// If we have a background and its not the default background
// then we apply background opacity
if (style.bg(cell, palette) != null and !rgb.eql(self.background_color)) {
break :bg_alpha default;
}
// We apply background opacity.
var bg_alpha: f64 = @floatFromInt(default);
bg_alpha *= self.config.background_opacity;
bg_alpha = @ceil(bg_alpha);
break :bg_alpha @intFromFloat(bg_alpha);
};
self.cells_bg.appendAssumeCapacity(.{
.mode = .rgb,
.grid_pos = .{ x, y },
.cell_width = cell.gridWidth(),
.color = .{ rgb.r, rgb.g, rgb.b, bg_alpha },
});
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
} else .{
self.current_background_color.r,
self.current_background_color.g,
self.current_background_color.b,
@intFromFloat(@max(0, @min(255, @round(self.config.background_opacity * 255)))),
};
// If the cell has a character, draw it
if (cell.hasText()) fg: {
// Render
const render = try self.font_grid.renderGlyph(
self.alloc,
shaper_run.font_index,
shaper_cell.glyph_index orelse break :fg,
.{
.grid_metrics = self.grid_metrics,
.thicken = self.config.font_thicken,
},
);
const mode: mtl_shaders.CellText.Mode = switch (try fgMode(
render.presentation,
cell_pin,
)) {
.normal => .fg,
.color => .fg_color,
.constrained => .fg_constrained,
};
self.cells_text.appendAssumeCapacity(.{
.mode = mode,
.grid_pos = .{ x, y },
.cell_width = cell.gridWidth(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.bg_color = bg,
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{
render.glyph.offset_x + shaper_cell.x_offset,
render.glyph.offset_y + shaper_cell.y_offset,
},
});
}
if (underline != .none) {
const sprite: font.Sprite = switch (underline) {
.none => unreachable,
.single => .underline,
.double => .underline_double,
.dotted => .underline_dotted,
.dashed => .underline_dashed,
.curly => .underline_curly,
};
const render = try self.font_grid.renderGlyph(
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
.{
.cell_width = if (cell.wide == .wide) 2 else 1,
.grid_metrics = self.grid_metrics,
},
);
const color = style.underlineColor(palette) orelse colors.fg;
self.cells_text.appendAssumeCapacity(.{
.mode = .fg,
.grid_pos = .{ x, y },
.cell_width = cell.gridWidth(),
.color = .{ color.r, color.g, color.b, alpha },
.bg_color = bg,
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
});
}
if (style.flags.strikethrough) {
const render = try self.font_grid.renderGlyph(
self.alloc,
font.sprite_index,
@intFromEnum(font.Sprite.strikethrough),
.{
.cell_width = if (cell.wide == .wide) 2 else 1,
.grid_metrics = self.grid_metrics,
},
);
self.cells_text.appendAssumeCapacity(.{
.mode = .fg,
.grid_pos = .{ x, y },
.cell_width = cell.gridWidth(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.bg_color = bg,
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
});
}
return true;
}
fn addCursor2(
fn addCursor(
self: *Metal,
screen: *terminal.Screen,
cursor_style: renderer.CursorStyle,
@ -2534,66 +2088,7 @@ fn addCursor2(
});
}
fn addCursor(
self: *Metal,
screen: *terminal.Screen,
cursor_style: renderer.CursorStyle,
) ?*const mtl_shaders.CellText {
// Add the cursor. We render the cursor over the wide character if
// we're on the wide characer tail.
const wide, const x = cell: {
// The cursor goes over the screen cursor position.
const cell = screen.cursor.page_cell;
if (cell.wide != .spacer_tail or screen.cursor.x == 0)
break :cell .{ cell.wide == .wide, screen.cursor.x };
// If we're part of a wide character, we move the cursor back to
// the actual character.
const prev_cell = screen.cursorCellLeft(1);
break :cell .{ prev_cell.wide == .wide, screen.cursor.x - 1 };
};
const color = self.cursor_color orelse self.foreground_color;
const alpha: u8 = if (!self.focused) 255 else alpha: {
const alpha = 255 * self.config.cursor_opacity;
break :alpha @intFromFloat(@ceil(alpha));
};
const sprite: font.Sprite = switch (cursor_style) {
.block => .cursor_rect,
.block_hollow => .cursor_hollow_rect,
.bar => .cursor_bar,
.underline => .underline,
};
const render = self.font_grid.renderGlyph(
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
.{
.cell_width = if (wide) 2 else 1,
.grid_metrics = self.grid_metrics,
},
) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err});
return null;
};
self.cells_text.appendAssumeCapacity(.{
.mode = .fg,
.grid_pos = .{ x, screen.cursor.y },
.cell_width = if (wide) 2 else 1,
.color = .{ color.r, color.g, color.b, alpha },
.bg_color = .{ 0, 0, 0, 0 },
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
});
return &self.cells_text.items[self.cells_text.items.len - 1];
}
fn addPreeditCell2(
fn addPreeditCell(
self: *Metal,
cp: renderer.State.Preedit.Codepoint,
coord: terminal.Coordinate,
@ -2639,53 +2134,6 @@ fn addPreeditCell2(
});
}
fn addPreeditCell(
self: *Metal,
cp: renderer.State.Preedit.Codepoint,
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
) !void {
// Preedit is rendered inverted
const bg = self.foreground_color;
const fg = self.background_color;
// Render the glyph for our preedit text
const render_ = self.font_grid.renderCodepoint(
self.alloc,
@intCast(cp.codepoint),
.regular,
.text,
.{ .grid_metrics = self.grid_metrics },
) catch |err| {
log.warn("error rendering preedit glyph err={}", .{err});
return;
};
const render = render_ orelse {
log.warn("failed to find font for preedit codepoint={X}", .{cp.codepoint});
return;
};
// Add our opaque background cell
self.cells_bg.appendAssumeCapacity(.{
.mode = .rgb,
.grid_pos = .{ x, y },
.cell_width = if (cp.wide) 2 else 1,
.color = .{ bg.r, bg.g, bg.b, 255 },
});
// Add our text
self.cells_text.appendAssumeCapacity(.{
.mode = .fg,
.grid_pos = .{ x, y },
.cell_width = if (cp.wide) 2 else 1,
.color = .{ fg.r, fg.g, fg.b, 255 },
.bg_color = .{ bg.r, bg.g, bg.b, 255 },
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
});
}
/// Sync the atlas data to the given texture. This copies the bytes
/// associated with the atlas to the given texture. If the atlas no longer
/// fits into the texture, the texture will be resized.