mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #2062 from qwerasd205/metal-optimizations
Metal optimizations
This commit is contained in:
@ -625,8 +625,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
.cell_size = undefined,
|
||||
.grid_size = undefined,
|
||||
.grid_padding = undefined,
|
||||
.padding_extend_top = true,
|
||||
.padding_extend_bottom = true,
|
||||
.padding_extend = .{},
|
||||
.min_contrast = options.config.min_contrast,
|
||||
.cursor_pos = .{ std.math.maxInt(u16), std.math.maxInt(u16) },
|
||||
.cursor_color = undefined,
|
||||
@ -1084,7 +1083,7 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
|
||||
// Setup our frame data
|
||||
try frame.uniforms.sync(self.gpu_state.device, &.{self.uniforms});
|
||||
const bg_count = try frame.cells_bg.syncFromArrayLists(self.gpu_state.device, self.cells.bg_rows.lists);
|
||||
try frame.cells_bg.sync(self.gpu_state.device, self.cells.bg_cells);
|
||||
const fg_count = try frame.cells.syncFromArrayLists(self.gpu_state.device, self.cells.fg_rows.lists);
|
||||
|
||||
// If we have custom shaders, update the animation time.
|
||||
@ -1179,7 +1178,7 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
||||
|
||||
// Then draw background cells
|
||||
try self.drawCellBgs(encoder, frame, bg_count);
|
||||
try self.drawCellBgs(encoder, frame);
|
||||
|
||||
// Then draw images under text
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
||||
@ -1371,9 +1370,9 @@ fn drawPostShader(
|
||||
void,
|
||||
objc.sel("drawPrimitives:vertexStart:vertexCount:"),
|
||||
.{
|
||||
@intFromEnum(mtl.MTLPrimitiveType.triangle_strip),
|
||||
@intFromEnum(mtl.MTLPrimitiveType.triangle),
|
||||
@as(c_ulong, 0),
|
||||
@as(c_ulong, 4),
|
||||
@as(c_ulong, 3),
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -1503,12 +1502,7 @@ fn drawCellBgs(
|
||||
self: *Metal,
|
||||
encoder: objc.Object,
|
||||
frame: *const FrameState,
|
||||
len: usize,
|
||||
) !void {
|
||||
// This triggers an assertion in the Metal API if we try to draw
|
||||
// with an instance count of 0 so just bail.
|
||||
if (len == 0) return;
|
||||
|
||||
// Use our shader pipeline
|
||||
encoder.msgSend(
|
||||
void,
|
||||
@ -1519,25 +1513,22 @@ fn drawCellBgs(
|
||||
// Set our buffers
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
objc.sel("setFragmentBuffer:offset:atIndex:"),
|
||||
.{ frame.cells_bg.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
|
||||
);
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
objc.sel("setFragmentBuffer:offset:atIndex:"),
|
||||
.{ frame.uniforms.buffer.value, @as(c_ulong, 0), @as(c_ulong, 1) },
|
||||
);
|
||||
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
||||
objc.sel("drawPrimitives:vertexStart:vertexCount:"),
|
||||
.{
|
||||
@intFromEnum(mtl.MTLPrimitiveType.triangle),
|
||||
@as(c_ulong, 6),
|
||||
@intFromEnum(mtl.MTLIndexType.uint16),
|
||||
self.gpu_state.instance.buffer.value,
|
||||
@as(c_ulong, 0),
|
||||
@as(c_ulong, len),
|
||||
@as(c_ulong, 3),
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -1571,6 +1562,11 @@ fn drawCellFgs(
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
.{ frame.uniforms.buffer.value, @as(c_ulong, 0), @as(c_ulong, 1) },
|
||||
);
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
.{ frame.cells_bg.buffer.value, @as(c_ulong, 0), @as(c_ulong, 2) },
|
||||
);
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setFragmentTexture:atIndex:"),
|
||||
@ -1953,16 +1949,20 @@ pub fn setScreenSize(
|
||||
const padded_dim = dim.subPadding(padding);
|
||||
|
||||
// Blank space around the grid.
|
||||
const blank: renderer.Padding = switch (self.config.padding_color) {
|
||||
// We can use zero padding because the background color is our
|
||||
// clear color.
|
||||
.background => .{},
|
||||
|
||||
.extend => dim.blankPadding(padding, grid_size, .{
|
||||
const blank: renderer.Padding = dim.blankPadding(padding, grid_size, .{
|
||||
.width = self.grid_metrics.cell_width,
|
||||
.height = self.grid_metrics.cell_height,
|
||||
}).add(padding),
|
||||
};
|
||||
}).add(padding);
|
||||
|
||||
var padding_extend = self.uniforms.padding_extend;
|
||||
if (self.config.padding_color == .extend) {
|
||||
// If padding extension is enabled, we extend left and right always.
|
||||
padding_extend.left = true;
|
||||
padding_extend.right = true;
|
||||
} else {
|
||||
// Otherwise, disable all padding extension.
|
||||
padding_extend = .{};
|
||||
}
|
||||
|
||||
// Set the size of the drawable surface to the bounds
|
||||
self.layer.setProperty("drawableSize", macos.graphics.Size{
|
||||
@ -1993,8 +1993,7 @@ pub fn setScreenSize(
|
||||
@floatFromInt(blank.bottom),
|
||||
@floatFromInt(blank.left),
|
||||
},
|
||||
.padding_extend_top = old.padding_extend_top,
|
||||
.padding_extend_bottom = old.padding_extend_bottom,
|
||||
.padding_extend = padding_extend,
|
||||
.min_contrast = old.min_contrast,
|
||||
.cursor_pos = old.cursor_pos,
|
||||
.cursor_color = old.cursor_color,
|
||||
@ -2139,8 +2138,10 @@ fn rebuildCells(
|
||||
self.cells.reset();
|
||||
|
||||
// We also reset our padding extension depending on the screen type
|
||||
self.uniforms.padding_extend_top = screen_type == .alternate;
|
||||
self.uniforms.padding_extend_bottom = screen_type == .alternate;
|
||||
if (self.config.padding_color == .extend) {
|
||||
self.uniforms.padding_extend.up = screen_type == .alternate;
|
||||
self.uniforms.padding_extend.down = screen_type == .alternate;
|
||||
}
|
||||
}
|
||||
|
||||
// Go row-by-row to build the cells. We go row by row because we do
|
||||
@ -2177,10 +2178,12 @@ fn rebuildCells(
|
||||
// under certain conditions we feel are safe. This helps make some
|
||||
// scenarios look better while avoiding scenarios we know do NOT look
|
||||
// good.
|
||||
if (self.config.padding_color == .extend) {
|
||||
if (y == 0 and screen_type == .primary) {
|
||||
self.uniforms.padding_extend_top = !row.neverExtendBg();
|
||||
self.uniforms.padding_extend.up = !row.neverExtendBg();
|
||||
} else if (y == self.cells.size.rows - 1 and screen_type == .primary) {
|
||||
self.uniforms.padding_extend_bottom = !row.neverExtendBg();
|
||||
self.uniforms.padding_extend.down = !row.neverExtendBg();
|
||||
}
|
||||
}
|
||||
|
||||
// Split our row into runs and shape each one.
|
||||
@ -2411,7 +2414,7 @@ fn updateCell(
|
||||
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: {
|
||||
if (colors.bg) |rgb| {
|
||||
// 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
|
||||
@ -2440,21 +2443,17 @@ fn updateCell(
|
||||
break :bg_alpha @intFromFloat(bg_alpha);
|
||||
};
|
||||
|
||||
try self.cells.add(self.alloc, .bg, .{
|
||||
.mode = .rgb,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.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)))),
|
||||
self.cells.bgCell(coord.y, coord.x).* = .{
|
||||
rgb.r, rgb.g, rgb.b, bg_alpha,
|
||||
};
|
||||
|
||||
if (cell.gridWidth() > 1 and coord.x < self.cells.size.columns - 1) {
|
||||
self.cells.bgCell(coord.y, coord.x).* = .{
|
||||
rgb.r, rgb.g, rgb.b, bg_alpha,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If the shaper cell has a glyph, draw it.
|
||||
if (shaper_cell.glyph_index) |glyph_index| glyph: {
|
||||
// Render
|
||||
@ -2487,14 +2486,13 @@ fn updateCell(
|
||||
try self.cells.add(self.alloc, .text, .{
|
||||
.mode = mode,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.y) },
|
||||
.cell_width = cell.gridWidth(),
|
||||
.constraint_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,
|
||||
.bearings = .{
|
||||
@intCast(render.glyph.offset_x + shaper_cell.x_offset),
|
||||
@intCast(render.glyph.offset_y + shaper_cell.y_offset),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -2524,12 +2522,14 @@ fn updateCell(
|
||||
try self.cells.add(self.alloc, .underline, .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.y) },
|
||||
.cell_width = cell.gridWidth(),
|
||||
.constraint_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 },
|
||||
.bearings = .{
|
||||
@intCast(render.glyph.offset_x),
|
||||
@intCast(render.glyph.offset_y),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -2547,12 +2547,14 @@ fn updateCell(
|
||||
try self.cells.add(self.alloc, .strikethrough, .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.y) },
|
||||
.cell_width = cell.gridWidth(),
|
||||
.constraint_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 },
|
||||
.bearings = .{
|
||||
@intCast(render.glyph.offset_x),
|
||||
@intCast(render.glyph.offset_y),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -2607,12 +2609,13 @@ fn addCursor(
|
||||
self.cells.setCursor(.{
|
||||
.mode = .cursor,
|
||||
.grid_pos = .{ x, screen.cursor.y },
|
||||
.cell_width = if (wide) 2 else 1,
|
||||
.color = .{ cursor_color.r, cursor_color.g, cursor_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 },
|
||||
.bearings = .{
|
||||
@intCast(render.glyph.offset_x),
|
||||
@intCast(render.glyph.offset_y),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -2642,23 +2645,26 @@ fn addPreeditCell(
|
||||
};
|
||||
|
||||
// Add our opaque background cell
|
||||
try self.cells.add(self.alloc, .bg, .{
|
||||
.mode = .rgb,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.y) },
|
||||
.cell_width = if (cp.wide) 2 else 1,
|
||||
.color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
});
|
||||
self.cells.bgCell(coord.y, coord.x).* = .{
|
||||
bg.r, bg.g, bg.b, 255,
|
||||
};
|
||||
if (cp.wide and coord.x < self.cells.size.columns - 1) {
|
||||
self.cells.bgCell(coord.y, coord.x + 1).* = .{
|
||||
bg.r, bg.g, bg.b, 255,
|
||||
};
|
||||
}
|
||||
|
||||
// Add our text
|
||||
try self.cells.add(self.alloc, .text, .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ @intCast(coord.x), @intCast(coord.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 },
|
||||
.bearings = .{
|
||||
@intCast(render.glyph.offset_x),
|
||||
@intCast(render.glyph.offset_y),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,11 +51,13 @@ pub const MTLIndexType = enum(c_ulong) {
|
||||
pub const MTLVertexFormat = enum(c_ulong) {
|
||||
uchar4 = 3,
|
||||
ushort2 = 13,
|
||||
short2 = 16,
|
||||
float2 = 29,
|
||||
float4 = 31,
|
||||
int2 = 33,
|
||||
uint = 36,
|
||||
uint2 = 37,
|
||||
uint4 = 39,
|
||||
uchar = 45,
|
||||
};
|
||||
|
||||
|
@ -75,22 +75,13 @@ fn ArrayListPool(comptime T: type) type {
|
||||
pub const Contents = struct {
|
||||
size: renderer.GridSize = .{ .rows = 0, .columns = 0 },
|
||||
|
||||
/// The ArrayListPool which holds all of the background cells. When sized
|
||||
/// with Contents.resize the individual ArrayLists SHOULD be given enough
|
||||
/// capacity that appendAssumeCapacity may be used, since it should be
|
||||
/// impossible for a row to have more background cells than columns.
|
||||
/// Flat array containing cell background colors for the terminal grid.
|
||||
///
|
||||
/// HOWEVER, the initial capacity can be exceeded due to multi-glyph
|
||||
/// composites each adding a background cell for the same position.
|
||||
/// This should probably be considered a bug, but for now it means
|
||||
/// that sometimes allocations might happen, so appendAssumeCapacity
|
||||
/// MUST NOT be used.
|
||||
/// Indexed as `bg_cells[row * size.columns + col]`.
|
||||
///
|
||||
/// Rows are indexed as Contents.bg_rows[y].
|
||||
///
|
||||
/// Must be initialized by calling resize on the Contents struct before
|
||||
/// calling any operations.
|
||||
bg_rows: ArrayListPool(mtl_shaders.CellBg) = .{},
|
||||
/// Prefer accessing with `Contents.bgCell(row, col).*` instead
|
||||
/// of directly indexing in order to avoid integer size bugs.
|
||||
bg_cells: []mtl_shaders.CellBg = undefined,
|
||||
|
||||
/// The ArrayListPool which holds all of the foreground cells. When sized
|
||||
/// with Contents.resize the individual ArrayLists are given enough room
|
||||
@ -116,7 +107,7 @@ pub const Contents = struct {
|
||||
fg_rows: ArrayListPool(mtl_shaders.CellText) = .{},
|
||||
|
||||
pub fn deinit(self: *Contents, alloc: Allocator) void {
|
||||
self.bg_rows.deinit(alloc);
|
||||
alloc.free(self.bg_cells);
|
||||
self.fg_rows.deinit(alloc);
|
||||
}
|
||||
|
||||
@ -129,15 +120,12 @@ pub const Contents = struct {
|
||||
) !void {
|
||||
self.size = size;
|
||||
|
||||
// When we create our bg_rows pool, we give the lists an initial
|
||||
// capacity of size.columns. This is to account for the usual case
|
||||
// where you have a row with normal text and background colors.
|
||||
// This can be exceeded due to multi-glyph composites each adding
|
||||
// a background cell for the same position. This should probably be
|
||||
// considered a bug, but for now it means that sometimes allocations
|
||||
// might happen, and appendAssumeCapacity MUST NOT be used.
|
||||
var bg_rows = try ArrayListPool(mtl_shaders.CellBg).init(alloc, size.rows, size.columns);
|
||||
errdefer bg_rows.deinit(alloc);
|
||||
const cell_count = @as(usize, size.columns) * @as(usize, size.rows);
|
||||
|
||||
const bg_cells = try alloc.alloc(mtl_shaders.CellBg, cell_count);
|
||||
errdefer alloc.free(bg_cells);
|
||||
|
||||
@memset(bg_cells, .{0, 0, 0, 0});
|
||||
|
||||
// The foreground lists can hold 3 types of items:
|
||||
// - Glyphs
|
||||
@ -154,10 +142,10 @@ pub const Contents = struct {
|
||||
var fg_rows = try ArrayListPool(mtl_shaders.CellText).init(alloc, size.rows + 1, size.columns * 3);
|
||||
errdefer fg_rows.deinit(alloc);
|
||||
|
||||
self.bg_rows.deinit(alloc);
|
||||
alloc.free(self.bg_cells);
|
||||
self.fg_rows.deinit(alloc);
|
||||
|
||||
self.bg_rows = bg_rows;
|
||||
self.bg_cells = bg_cells;
|
||||
self.fg_rows = fg_rows;
|
||||
|
||||
// We don't need 3*cols worth of cells for the cursor list, so we can
|
||||
@ -170,7 +158,7 @@ pub const Contents = struct {
|
||||
|
||||
/// Reset the cell contents to an empty state without resizing.
|
||||
pub fn reset(self: *Contents) void {
|
||||
self.bg_rows.reset();
|
||||
@memset(self.bg_cells, .{ 0, 0, 0, 0 });
|
||||
self.fg_rows.reset();
|
||||
}
|
||||
|
||||
@ -183,6 +171,12 @@ pub const Contents = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a background cell. Prefer this function over direct indexing
|
||||
/// of `bg_cells` in order to avoid integer size bugs causing overflows.
|
||||
pub inline fn bgCell(self: *Contents, row: usize, col: usize) *mtl_shaders.CellBg {
|
||||
return &self.bg_cells[row * self.size.columns + col];
|
||||
}
|
||||
|
||||
/// Add a cell to the appropriate list. Adding the same cell twice will
|
||||
/// result in duplication in the vertex buffer. The caller should clear
|
||||
/// the corresponding row with Contents.clear to remove old cells first.
|
||||
@ -197,7 +191,7 @@ pub const Contents = struct {
|
||||
assert(y < self.size.rows);
|
||||
|
||||
switch (key) {
|
||||
.bg => try self.bg_rows.lists[y].append(alloc, cell),
|
||||
.bg => comptime unreachable,
|
||||
|
||||
.text,
|
||||
.underline,
|
||||
@ -213,7 +207,8 @@ pub const Contents = struct {
|
||||
pub fn clear(self: *Contents, y: terminal.size.CellCountInt) void {
|
||||
assert(y < self.size.rows);
|
||||
|
||||
self.bg_rows.lists[y].clearRetainingCapacity();
|
||||
@memset(self.bg_cells[@as(usize, y) * self.size.columns ..][0..self.size.columns], .{ 0, 0, 0, 0 });
|
||||
|
||||
// We have a special list containing the cursor cell at the start
|
||||
// of our fg row pool, so we need to add 1 to the y to get the
|
||||
// correct index.
|
||||
@ -234,47 +229,42 @@ test Contents {
|
||||
|
||||
// We should start off empty after resizing.
|
||||
for (0..rows) |y| {
|
||||
try testing.expect(c.bg_rows.lists[y].items.len == 0);
|
||||
try testing.expect(c.fg_rows.lists[y + 1].items.len == 0);
|
||||
for (0..cols) |x| {
|
||||
try testing.expectEqual(.{0, 0, 0, 0}, c.bgCell(y, x).*);
|
||||
}
|
||||
}
|
||||
// And the cursor row should have a capacity of 1 and also be empty.
|
||||
try testing.expect(c.fg_rows.lists[0].capacity == 1);
|
||||
try testing.expect(c.fg_rows.lists[0].items.len == 0);
|
||||
|
||||
// Add some contents.
|
||||
const bg_cell: mtl_shaders.CellBg = .{
|
||||
.mode = .rgb,
|
||||
.grid_pos = .{ 4, 1 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
const bg_cell: mtl_shaders.CellBg = .{ 0, 0, 0, 1 };
|
||||
const fg_cell: mtl_shaders.CellText = .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ 4, 1 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
.bg_color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
try c.add(alloc, .bg, bg_cell);
|
||||
c.bgCell(1, 4).* = bg_cell;
|
||||
try c.add(alloc, .text, fg_cell);
|
||||
try testing.expectEqual(bg_cell, c.bg_rows.lists[1].items[0]);
|
||||
try testing.expectEqual(bg_cell, c.bgCell(1, 4).*);
|
||||
// The fg row index is offset by 1 because of the cursor list.
|
||||
try testing.expectEqual(fg_cell, c.fg_rows.lists[2].items[0]);
|
||||
|
||||
// And we should be able to clear it.
|
||||
c.clear(1);
|
||||
for (0..rows) |y| {
|
||||
try testing.expect(c.bg_rows.lists[y].items.len == 0);
|
||||
try testing.expect(c.fg_rows.lists[y + 1].items.len == 0);
|
||||
for (0..cols) |x| {
|
||||
try testing.expectEqual(.{0, 0, 0, 0}, c.bgCell(y, x).*);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a cursor.
|
||||
const cursor_cell: mtl_shaders.CellText = .{
|
||||
.mode = .cursor,
|
||||
.grid_pos = .{ 2, 3 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
.bg_color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
c.setCursor(cursor_cell);
|
||||
try testing.expectEqual(cursor_cell, c.fg_rows.lists[0].items[0]);
|
||||
@ -296,24 +286,32 @@ test "Contents clear retains other content" {
|
||||
defer c.deinit(alloc);
|
||||
|
||||
// Set some contents
|
||||
const cell1: mtl_shaders.CellBg = .{
|
||||
.mode = .rgb,
|
||||
// bg and fg cells in row 1
|
||||
const bg_cell_1: mtl_shaders.CellBg = .{ 0, 0, 0, 1 };
|
||||
const fg_cell_1: mtl_shaders.CellText = .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ 4, 1 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
const cell2: mtl_shaders.CellBg = .{
|
||||
.mode = .rgb,
|
||||
c.bgCell(1, 4).* = bg_cell_1;
|
||||
try c.add(alloc, .text, fg_cell_1);
|
||||
// bg and fg cells in row 2
|
||||
const bg_cell_2: mtl_shaders.CellBg = .{ 0, 0, 0, 1 };
|
||||
const fg_cell_2: mtl_shaders.CellText = .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ 4, 2 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
try c.add(alloc, .bg, cell1);
|
||||
try c.add(alloc, .bg, cell2);
|
||||
c.bgCell(2, 4).* = bg_cell_2;
|
||||
try c.add(alloc, .text, fg_cell_2);
|
||||
|
||||
// Clear row 1, this should leave row 2 untouched
|
||||
c.clear(1);
|
||||
|
||||
// Row 2 should still contain its cell.
|
||||
try testing.expectEqual(cell2, c.bg_rows.lists[2].items[0]);
|
||||
// Row 2 should still contain its cells.
|
||||
try testing.expectEqual(bg_cell_2, c.bgCell(2, 4).*);
|
||||
// Fg row index is +1 because of cursor list at start
|
||||
try testing.expectEqual(fg_cell_2, c.fg_rows.lists[3].items[0]);
|
||||
}
|
||||
|
||||
test "Contents clear last added content" {
|
||||
@ -328,22 +326,30 @@ test "Contents clear last added content" {
|
||||
defer c.deinit(alloc);
|
||||
|
||||
// Set some contents
|
||||
const cell1: mtl_shaders.CellBg = .{
|
||||
.mode = .rgb,
|
||||
// bg and fg cells in row 1
|
||||
const bg_cell_1: mtl_shaders.CellBg = .{ 0, 0, 0, 1 };
|
||||
const fg_cell_1: mtl_shaders.CellText = .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ 4, 1 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
const cell2: mtl_shaders.CellBg = .{
|
||||
.mode = .rgb,
|
||||
c.bgCell(1, 4).* = bg_cell_1;
|
||||
try c.add(alloc, .text, fg_cell_1);
|
||||
// bg and fg cells in row 2
|
||||
const bg_cell_2: mtl_shaders.CellBg = .{ 0, 0, 0, 1 };
|
||||
const fg_cell_2: mtl_shaders.CellText = .{
|
||||
.mode = .fg,
|
||||
.grid_pos = .{ 4, 2 },
|
||||
.cell_width = 1,
|
||||
.color = .{ 0, 0, 0, 1 },
|
||||
};
|
||||
try c.add(alloc, .bg, cell1);
|
||||
try c.add(alloc, .bg, cell2);
|
||||
c.bgCell(2, 4).* = bg_cell_2;
|
||||
try c.add(alloc, .text, fg_cell_2);
|
||||
|
||||
// Clear row 2, this should leave row 1 untouched
|
||||
c.clear(2);
|
||||
|
||||
// Row 1 should still contain its cell.
|
||||
try testing.expectEqual(cell1, c.bg_rows.lists[1].items[0]);
|
||||
// Row 1 should still contain its cells.
|
||||
try testing.expectEqual(bg_cell_1, c.bgCell(1, 4).*);
|
||||
// Fg row index is +1 because of cursor list at start
|
||||
try testing.expectEqual(fg_cell_1, c.fg_rows.lists[2].items[0]);
|
||||
}
|
||||
|
@ -124,9 +124,10 @@ pub const Uniforms = extern struct {
|
||||
/// top, right, bottom, left.
|
||||
grid_padding: [4]f32 align(16),
|
||||
|
||||
/// True if vertical padding gets the extended color of the nearest row.
|
||||
padding_extend_top: bool align(1),
|
||||
padding_extend_bottom: bool align(1),
|
||||
/// Bit mask defining which directions to
|
||||
/// extend cell colors in to the padding.
|
||||
/// Order, LSB first: left, right, up, down
|
||||
padding_extend: PaddingExtend align(1),
|
||||
|
||||
/// The minimum contrast ratio for text. The contrast ratio is calculated
|
||||
/// according to the WCAG 2.0 spec.
|
||||
@ -135,6 +136,14 @@ pub const Uniforms = extern struct {
|
||||
/// The cursor position and color.
|
||||
cursor_pos: [2]u16 align(4),
|
||||
cursor_color: [4]u8 align(4),
|
||||
|
||||
const PaddingExtend = packed struct(u8) {
|
||||
left: bool = false,
|
||||
right: bool = false,
|
||||
up: bool = false,
|
||||
down: bool = false,
|
||||
_padding: u4 = 0,
|
||||
};
|
||||
};
|
||||
|
||||
/// The uniforms used for custom postprocess shaders.
|
||||
@ -246,7 +255,7 @@ fn initPostPipeline(
|
||||
// Get our vertex and fragment functions
|
||||
const func_vert = func_vert: {
|
||||
const str = try macos.foundation.String.createWithBytes(
|
||||
"post_vertex",
|
||||
"full_screen_vertex",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
@ -307,14 +316,13 @@ fn initPostPipeline(
|
||||
|
||||
/// This is a single parameter for the terminal cell shader.
|
||||
pub const CellText = extern struct {
|
||||
mode: Mode,
|
||||
glyph_pos: [2]u32 = .{ 0, 0 },
|
||||
glyph_size: [2]u32 = .{ 0, 0 },
|
||||
glyph_offset: [2]i32 = .{ 0, 0 },
|
||||
color: [4]u8,
|
||||
bg_color: [4]u8,
|
||||
grid_pos: [2]u16,
|
||||
cell_width: u8,
|
||||
glyph_pos: [2]u32 align(8) = .{ 0, 0 },
|
||||
glyph_size: [2]u32 align(8) = .{ 0, 0 },
|
||||
bearings: [2]i16 align(4) = .{ 0, 0 },
|
||||
grid_pos: [2]u16 align(4),
|
||||
color: [4]u8 align(4),
|
||||
mode: Mode align(1),
|
||||
constraint_width: u8 align(1) = 0,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
fg = 1,
|
||||
@ -323,6 +331,12 @@ pub const CellText = extern struct {
|
||||
cursor = 4,
|
||||
fg_powerline = 5,
|
||||
};
|
||||
|
||||
test {
|
||||
// Minimizing the size of this struct is important,
|
||||
// so we test it in order to be aware of any changes.
|
||||
try std.testing.expectEqual(32, @sizeOf(CellText));
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize the cell render pipeline for our shader library.
|
||||
@ -367,94 +381,7 @@ fn initCellTextPipeline(device: objc.Object, library: objc.Object) !objc.Object
|
||||
|
||||
// Our attributes are the fields of the input
|
||||
const attrs = objc.Object.fromId(desc.getProperty(?*anyopaque, "attributes"));
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 0)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "mode")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 1)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.ushort2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 2)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 3)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_size")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 4)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.int2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_offset")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 5)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 7)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "bg_color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 6)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "cell_width")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
autoAttribute(CellText, attrs);
|
||||
|
||||
// The layout describes how and when we fetch the next vertex input.
|
||||
const layouts = objc.Object.fromId(desc.getProperty(?*anyopaque, "layouts"));
|
||||
@ -525,23 +452,14 @@ fn initCellTextPipeline(device: objc.Object, library: objc.Object) !objc.Object
|
||||
}
|
||||
|
||||
/// This is a single parameter for the cell bg shader.
|
||||
pub const CellBg = extern struct {
|
||||
mode: Mode,
|
||||
grid_pos: [2]u16,
|
||||
color: [4]u8,
|
||||
cell_width: u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
rgb = 1,
|
||||
};
|
||||
};
|
||||
pub const CellBg = [4]u8;
|
||||
|
||||
/// Initialize the cell background render pipeline for our shader library.
|
||||
fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
// Get our vertex and fragment functions
|
||||
const func_vert = func_vert: {
|
||||
const str = try macos.foundation.String.createWithBytes(
|
||||
"cell_bg_vertex",
|
||||
"full_screen_vertex",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
@ -585,41 +503,8 @@ fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
.{@as(c_ulong, 0)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "mode")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 1)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.ushort2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 2)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "cell_width")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 3)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "color")));
|
||||
attr.setProperty("offset", @as(c_ulong, 0));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
|
||||
@ -733,50 +618,7 @@ fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
|
||||
// Our attributes are the fields of the input
|
||||
const attrs = objc.Object.fromId(desc.getProperty(?*anyopaque, "attributes"));
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 1)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Image, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 2)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Image, "cell_offset")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 3)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Image, "source_rect")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 4)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Image, "dest_size")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
autoAttribute(Image, attrs);
|
||||
|
||||
// The layout describes how and when we fetch the next vertex input.
|
||||
const layouts = objc.Object.fromId(desc.getProperty(?*anyopaque, "layouts"));
|
||||
@ -845,6 +687,41 @@ fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
return pipeline_state;
|
||||
}
|
||||
|
||||
fn autoAttribute(T: type, attrs: objc.Object) void {
|
||||
inline for (@typeInfo(T).Struct.fields, 0..) |field, i| {
|
||||
const offset = @offsetOf(T, field.name);
|
||||
|
||||
const FT = switch (@typeInfo(field.type)) {
|
||||
.Enum => |e| e.tag_type,
|
||||
else => field.type,
|
||||
};
|
||||
|
||||
const format = switch (FT) {
|
||||
[4]u8 => mtl.MTLVertexFormat.uchar4,
|
||||
[2]u16 => mtl.MTLVertexFormat.ushort2,
|
||||
[2]i16 => mtl.MTLVertexFormat.short2,
|
||||
[2]f32 => mtl.MTLVertexFormat.float2,
|
||||
[4]f32 => mtl.MTLVertexFormat.float4,
|
||||
[2]i32 => mtl.MTLVertexFormat.int2,
|
||||
u32 => mtl.MTLVertexFormat.uint,
|
||||
[2]u32 => mtl.MTLVertexFormat.uint2,
|
||||
[4]u32 => mtl.MTLVertexFormat.uint4,
|
||||
u8 => mtl.MTLVertexFormat.uchar,
|
||||
else => comptime unreachable,
|
||||
};
|
||||
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, i)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(format));
|
||||
attr.setProperty("offset", @as(c_ulong, offset));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
}
|
||||
|
||||
fn checkError(err_: ?*anyopaque) !void {
|
||||
const nserr = objc.Object.fromId(err_ orelse return);
|
||||
const str = @as(
|
||||
@ -855,27 +732,3 @@ fn checkError(err_: ?*anyopaque) !void {
|
||||
log.err("metal error={s}", .{str.cstringPtr(.ascii).?});
|
||||
return error.MetalFailed;
|
||||
}
|
||||
|
||||
// Intel macOS 13 doesn't like it when any field in a vertex buffer is not
|
||||
// aligned on the alignment of the struct. I don't understand it, I think
|
||||
// this must be some macOS 13 Metal GPU driver bug because it doesn't matter
|
||||
// on macOS 12 or Apple Silicon macOS 13.
|
||||
//
|
||||
// To be safe, we put this test in here.
|
||||
test "CellText offsets" {
|
||||
const testing = std.testing;
|
||||
const alignment = @alignOf(CellText);
|
||||
inline for (@typeInfo(CellText).Struct.fields) |field| {
|
||||
const offset = @offsetOf(CellText, field.name);
|
||||
try testing.expectEqual(0, @mod(offset, alignment));
|
||||
}
|
||||
}
|
||||
|
||||
test "CellBg offsets" {
|
||||
const testing = std.testing;
|
||||
const alignment = @alignOf(CellBg);
|
||||
inline for (@typeInfo(CellBg).Struct.fields) |field| {
|
||||
const offset = @offsetOf(CellBg, field.name);
|
||||
try testing.expectEqual(0, @mod(offset, alignment));
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,19 @@
|
||||
|
||||
using namespace metal;
|
||||
|
||||
enum Padding : uint8_t {
|
||||
EXTEND_LEFT = 1u,
|
||||
EXTEND_RIGHT = 2u,
|
||||
EXTEND_UP = 4u,
|
||||
EXTEND_DOWN = 8u,
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
float4x4 projection_matrix;
|
||||
float2 cell_size;
|
||||
ushort2 grid_size;
|
||||
float4 grid_padding;
|
||||
bool padding_extend_top;
|
||||
bool padding_extend_bottom;
|
||||
uint8_t padding_extend;
|
||||
float min_contrast;
|
||||
ushort2 cursor_pos;
|
||||
uchar4 cursor_color;
|
||||
@ -64,93 +70,86 @@ float4 contrasted_color(float min, float4 fg, float4 bg) {
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Cell Background Shader
|
||||
// Full Screen Vertex Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Cell BG Shader
|
||||
#pragma mark - Full Screen Vertex Shader
|
||||
|
||||
// The possible modes that a cell bg entry can take.
|
||||
enum CellBgMode : uint8_t {
|
||||
MODE_RGB = 1u,
|
||||
};
|
||||
|
||||
struct CellBgVertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
ushort2 grid_pos [[attribute(1)]];
|
||||
|
||||
// The color. For BG modes, this is the bg color, for FG modes this is
|
||||
// the text color. For styles, this is the color of the style.
|
||||
uchar4 color [[attribute(3)]];
|
||||
|
||||
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||
uint8_t cell_width [[attribute(2)]];
|
||||
};
|
||||
|
||||
struct CellBgVertexOut {
|
||||
struct FullScreenVertexOut {
|
||||
float4 position [[position]];
|
||||
float4 color;
|
||||
};
|
||||
|
||||
vertex CellBgVertexOut cell_bg_vertex(unsigned int vid [[vertex_id]],
|
||||
CellBgVertexIn input [[stage_in]],
|
||||
constant Uniforms& uniforms
|
||||
[[buffer(1)]]) {
|
||||
// Convert the grid x,y into world space x, y by accounting for cell size
|
||||
float2 cell_pos = uniforms.cell_size * float2(input.grid_pos);
|
||||
vertex FullScreenVertexOut full_screen_vertex(
|
||||
uint vid [[vertex_id]]
|
||||
) {
|
||||
FullScreenVertexOut out;
|
||||
|
||||
// Scaled cell size for the cell width
|
||||
float2 cell_size_scaled = uniforms.cell_size;
|
||||
cell_size_scaled.x = cell_size_scaled.x * input.cell_width;
|
||||
float4 position;
|
||||
position.x = (vid == 2) ? 3.0 : -1.0;
|
||||
position.y = (vid == 0) ? -3.0 : 1.0;
|
||||
position.zw = 1.0;
|
||||
|
||||
// If we're at the edge of the grid, we add our padding to the background
|
||||
// to extend it. Note: grid_padding is top/right/bottom/left. We always
|
||||
// extend horiziontally because there is no downside but there are various
|
||||
// heuristics to disable vertical extension.
|
||||
if (input.grid_pos.y == 0 && uniforms.padding_extend_top) {
|
||||
cell_pos.y -= uniforms.grid_padding.r;
|
||||
cell_size_scaled.y += uniforms.grid_padding.r;
|
||||
} else if (input.grid_pos.y == uniforms.grid_size.y - 1 &&
|
||||
uniforms.padding_extend_bottom) {
|
||||
cell_size_scaled.y += uniforms.grid_padding.b;
|
||||
}
|
||||
if (input.grid_pos.x == 0) {
|
||||
cell_pos.x -= uniforms.grid_padding.a;
|
||||
cell_size_scaled.x += uniforms.grid_padding.a;
|
||||
} else if (input.grid_pos.x == uniforms.grid_size.x - 1) {
|
||||
cell_size_scaled.x += uniforms.grid_padding.g;
|
||||
}
|
||||
|
||||
// Turn the cell position into a vertex point depending on the
|
||||
// vertex ID. Since we use instanced drawing, we have 4 vertices
|
||||
// for each corner of the cell. We can use vertex ID to determine
|
||||
// which one we're looking at. Using this, we can use 1 or 0 to keep
|
||||
// or discard the value for the vertex.
|
||||
// Single triangle is clipped to viewport.
|
||||
//
|
||||
// 0 = top-right
|
||||
// 1 = bot-right
|
||||
// 2 = bot-left
|
||||
// 3 = top-left
|
||||
float2 position;
|
||||
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
// X <- vid == 0: (-1, -3)
|
||||
// |\
|
||||
// | \
|
||||
// | \
|
||||
// |###\
|
||||
// |#+# \ `+` is (0, 0). `#`s are viewport area.
|
||||
// |### \
|
||||
// X------X <- vid == 2: (3, 1)
|
||||
// ^
|
||||
// vid == 1: (-1, 1)
|
||||
|
||||
// Calculate the final position of our cell in world space.
|
||||
// We have to add our cell size since our vertices are offset
|
||||
// one cell up and to the left. (Do the math to verify yourself)
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
|
||||
CellBgVertexOut out;
|
||||
out.color = float4(input.color) / 255.0f;
|
||||
out.position =
|
||||
uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||
out.position = position;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 cell_bg_fragment(CellBgVertexOut in [[stage_in]]) {
|
||||
return in.color;
|
||||
//-------------------------------------------------------------------
|
||||
// Cell Background Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Cell BG Shader
|
||||
|
||||
fragment float4 cell_bg_fragment(
|
||||
FullScreenVertexOut in [[stage_in]],
|
||||
constant uchar4 *cells [[buffer(0)]],
|
||||
constant Uniforms& uniforms [[buffer(1)]]
|
||||
) {
|
||||
int2 grid_pos = int2(floor((in.position.xy - uniforms.grid_padding.wx) / uniforms.cell_size));
|
||||
|
||||
// Clamp x position, extends edge bg colors in to padding on sides.
|
||||
if (grid_pos.x < 0) {
|
||||
if (uniforms.padding_extend & EXTEND_LEFT) {
|
||||
grid_pos.x = 0;
|
||||
} else {
|
||||
return float4(0.0);
|
||||
}
|
||||
} else if (grid_pos.x > uniforms.grid_size.x - 1) {
|
||||
if (uniforms.padding_extend & EXTEND_RIGHT) {
|
||||
grid_pos.x = uniforms.grid_size.x - 1;
|
||||
} else {
|
||||
return float4(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp y position if we should extend, otherwise discard if out of bounds.
|
||||
if (grid_pos.y < 0) {
|
||||
if (uniforms.padding_extend & EXTEND_UP) {
|
||||
grid_pos.y = 0;
|
||||
} else {
|
||||
return float4(0.0);
|
||||
}
|
||||
} else if (grid_pos.y > uniforms.grid_size.y - 1) {
|
||||
if (uniforms.padding_extend & EXTEND_DOWN) {
|
||||
grid_pos.y = uniforms.grid_size.y - 1;
|
||||
} else {
|
||||
return float4(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve color for cell and return it.
|
||||
return float4(cells[grid_pos.y * uniforms.grid_size.x + grid_pos.x]) / 255.0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
@ -168,51 +167,43 @@ enum CellTextMode : uint8_t {
|
||||
};
|
||||
|
||||
struct CellTextVertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
ushort2 grid_pos [[attribute(1)]];
|
||||
|
||||
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||
uint8_t cell_width [[attribute(6)]];
|
||||
|
||||
// The color of the rendered text glyph.
|
||||
uchar4 color [[attribute(5)]];
|
||||
|
||||
// The background color of the cell. This is used to determine if
|
||||
// we need to render the text with a different color to ensure
|
||||
// contrast.
|
||||
uchar4 bg_color [[attribute(7)]];
|
||||
|
||||
// The position of the glyph in the texture (x, y)
|
||||
uint2 glyph_pos [[attribute(2)]];
|
||||
uint2 glyph_pos [[attribute(0)]];
|
||||
|
||||
// The size of the glyph in the texture (w, h)
|
||||
uint2 glyph_size [[attribute(3)]];
|
||||
uint2 glyph_size [[attribute(1)]];
|
||||
|
||||
// The left and top bearings for the glyph (x, y)
|
||||
int2 glyph_offset [[attribute(4)]];
|
||||
int2 bearings [[attribute(2)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
ushort2 grid_pos [[attribute(3)]];
|
||||
|
||||
// The color of the rendered text glyph.
|
||||
uchar4 color [[attribute(4)]];
|
||||
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(5)]];
|
||||
|
||||
// The width to constrain the glyph to, in cells, or 0 for no constraint.
|
||||
uint8_t constraint_width [[attribute(6)]];
|
||||
};
|
||||
|
||||
struct CellTextVertexOut {
|
||||
float4 position [[position]];
|
||||
float2 cell_size;
|
||||
uint8_t mode;
|
||||
float4 color;
|
||||
float2 tex_coord;
|
||||
};
|
||||
|
||||
vertex CellTextVertexOut cell_text_vertex(unsigned int vid [[vertex_id]],
|
||||
CellTextVertexIn input [[stage_in]],
|
||||
constant Uniforms& uniforms
|
||||
[[buffer(1)]]) {
|
||||
vertex CellTextVertexOut cell_text_vertex(
|
||||
uint vid [[vertex_id]],
|
||||
CellTextVertexIn in [[stage_in]],
|
||||
constant Uniforms& uniforms [[buffer(1)]],
|
||||
constant uchar4 *bg_colors [[buffer(2)]]
|
||||
) {
|
||||
// Convert the grid x, y into world space x, y by accounting for cell size
|
||||
float2 cell_pos = uniforms.cell_size * float2(input.grid_pos);
|
||||
|
||||
// Scaled cell size for the cell width
|
||||
float2 cell_size_scaled = uniforms.cell_size;
|
||||
cell_size_scaled.x = cell_size_scaled.x * input.cell_width;
|
||||
float2 cell_pos = uniforms.cell_size * float2(in.grid_pos);
|
||||
|
||||
// Turn the cell position into a vertex point depending on the
|
||||
// vertex ID. Since we use instanced drawing, we have 4 vertices
|
||||
@ -224,44 +215,68 @@ vertex CellTextVertexOut cell_text_vertex(unsigned int vid [[vertex_id]],
|
||||
// 1 = bot-right
|
||||
// 2 = bot-left
|
||||
// 3 = top-left
|
||||
float2 position;
|
||||
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
float2 corner;
|
||||
corner.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
corner.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
|
||||
CellTextVertexOut out;
|
||||
out.mode = input.mode;
|
||||
out.cell_size = uniforms.cell_size;
|
||||
out.color = float4(input.color) / 255.0f;
|
||||
out.mode = in.mode;
|
||||
out.color = float4(in.color) / 255.0f;
|
||||
|
||||
float2 glyph_size = float2(input.glyph_size);
|
||||
float2 glyph_offset = float2(input.glyph_offset);
|
||||
// === Grid Cell ===
|
||||
//
|
||||
// offset.x = bearings.x
|
||||
// .|.
|
||||
// | |
|
||||
// +-------+_.
|
||||
// ._| | |
|
||||
// | | .###. | |
|
||||
// | | #...# | +- bearings.y
|
||||
// glyph_size.y -+ | ##### | |
|
||||
// | | #.... | |
|
||||
// ^ |_| .#### |_| _.
|
||||
// | | | +- offset.y = cell_size.y - bearings.y
|
||||
// . cell_pos -> +-------+ -'
|
||||
// +Y. |_._|
|
||||
// . |
|
||||
// | glyph_size.x
|
||||
// 0,0--...->
|
||||
// +X
|
||||
//
|
||||
// In order to get the bottom left of the glyph, we compute an offset based
|
||||
// on the bearings. The Y bearing is the distance from the top of the cell
|
||||
// to the bottom of the glyph, so we subtract it from the cell height to get
|
||||
// the y offset. The X bearing is the distance from the left of the cell to
|
||||
// the left of the glyph, so it works as the x offset directly.
|
||||
|
||||
// The glyph_offset.y is the y bearing, a y value that when added
|
||||
// to the baseline is the offset (+y is up). Our grid goes down.
|
||||
// So we flip it with `cell_size.y - glyph_offset.y`.
|
||||
glyph_offset.y = cell_size_scaled.y - glyph_offset.y;
|
||||
float2 size = float2(in.glyph_size);
|
||||
float2 offset = float2(in.bearings);
|
||||
|
||||
offset.y = uniforms.cell_size.y - offset.y;
|
||||
|
||||
// If we're constrained then we need to scale the glyph.
|
||||
// We also always constrain colored glyphs since we should have
|
||||
// their scaled cell size exactly correct.
|
||||
if (input.mode == MODE_TEXT_CONSTRAINED || input.mode == MODE_TEXT_COLOR) {
|
||||
if (glyph_size.x > cell_size_scaled.x) {
|
||||
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
|
||||
glyph_offset.y += (glyph_size.y - new_y) / 2;
|
||||
glyph_size.y = new_y;
|
||||
glyph_size.x = cell_size_scaled.x;
|
||||
if (in.mode == MODE_TEXT_CONSTRAINED || in.mode == MODE_TEXT_COLOR) {
|
||||
float max_width = uniforms.cell_size.x * in.constraint_width;
|
||||
if (size.x > max_width) {
|
||||
float new_y = size.y * (max_width / size.x);
|
||||
offset.y += (size.y - new_y) / 2;
|
||||
size.y = new_y;
|
||||
size.x = max_width;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the final position of the cell which uses our glyph size
|
||||
// and glyph offset to create the correct bounding box for the glyph.
|
||||
cell_pos = cell_pos + glyph_size * position + glyph_offset;
|
||||
cell_pos = cell_pos + size * corner + offset;
|
||||
out.position =
|
||||
uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||
|
||||
// Calculate the texture coordinate in pixels. This is NOT normalized
|
||||
// (between 0.0 and 1.0) and must be done in the fragment shader.
|
||||
out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position;
|
||||
// (between 0.0 and 1.0), and does not need to be, since the texture will
|
||||
// be sampled with pixel coordinate mode.
|
||||
out.tex_coord = float2(in.glyph_pos) + float2(in.glyph_size) * corner;
|
||||
|
||||
// If we have a minimum contrast, we need to check if we need to
|
||||
// change the color of the text to ensure it has enough contrast
|
||||
@ -270,27 +285,33 @@ vertex CellTextVertexOut cell_text_vertex(unsigned int vid [[vertex_id]],
|
||||
// since we want color glyphs to appear in their original color
|
||||
// and Powerline glyphs to be unaffected (else parts of the line would
|
||||
// have different colors as some parts are displayed via background colors).
|
||||
if (uniforms.min_contrast > 1.0f && input.mode == MODE_TEXT) {
|
||||
float4 bg_color = float4(input.bg_color) / 255.0f;
|
||||
if (uniforms.min_contrast > 1.0f && in.mode == MODE_TEXT) {
|
||||
float4 bg_color = float4(bg_colors[in.grid_pos.y * uniforms.grid_size.x + in.grid_pos.x]) / 255.0f;
|
||||
out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
||||
}
|
||||
|
||||
// If this cell is the cursor cell, then we need to change the color.
|
||||
if (input.mode != MODE_TEXT_CURSOR &&
|
||||
input.grid_pos.x == uniforms.cursor_pos.x &&
|
||||
input.grid_pos.y == uniforms.cursor_pos.y) {
|
||||
if (
|
||||
in.mode != MODE_TEXT_CURSOR &&
|
||||
in.grid_pos.x == uniforms.cursor_pos.x &&
|
||||
in.grid_pos.y == uniforms.cursor_pos.y
|
||||
) {
|
||||
out.color = float4(uniforms.cursor_color) / 255.0f;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 cell_text_fragment(CellTextVertexOut in [[stage_in]],
|
||||
texture2d<float> textureGreyscale
|
||||
[[texture(0)]],
|
||||
texture2d<float> textureColor
|
||||
[[texture(1)]]) {
|
||||
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
||||
fragment float4 cell_text_fragment(
|
||||
CellTextVertexOut in [[stage_in]],
|
||||
texture2d<float> textureGreyscale [[texture(0)]],
|
||||
texture2d<float> textureColor [[texture(1)]]
|
||||
) {
|
||||
constexpr sampler textureSampler(
|
||||
coord::pixel,
|
||||
address::clamp_to_edge,
|
||||
filter::nearest
|
||||
);
|
||||
|
||||
switch (in.mode) {
|
||||
default:
|
||||
@ -298,26 +319,20 @@ fragment float4 cell_text_fragment(CellTextVertexOut in [[stage_in]],
|
||||
case MODE_TEXT_CONSTRAINED:
|
||||
case MODE_TEXT_POWERLINE:
|
||||
case MODE_TEXT: {
|
||||
// Normalize the texture coordinates to [0,1]
|
||||
float2 size =
|
||||
float2(textureGreyscale.get_width(), textureGreyscale.get_height());
|
||||
float2 coord = in.tex_coord / size;
|
||||
|
||||
// We premult the alpha to our whole color since our blend function
|
||||
// uses One/OneMinusSourceAlpha to avoid blurry edges.
|
||||
// We first premult our given color.
|
||||
float4 premult = float4(in.color.rgb * in.color.a, in.color.a);
|
||||
|
||||
// Then premult the texture color
|
||||
float a = textureGreyscale.sample(textureSampler, coord).r;
|
||||
float a = textureGreyscale.sample(textureSampler, in.tex_coord).r;
|
||||
premult = premult * a;
|
||||
|
||||
return premult;
|
||||
}
|
||||
|
||||
case MODE_TEXT_COLOR: {
|
||||
// Normalize the texture coordinates to [0,1]
|
||||
float2 size = float2(textureColor.get_width(), textureColor.get_height());
|
||||
float2 coord = in.tex_coord / size;
|
||||
return textureColor.sample(textureSampler, coord);
|
||||
return textureColor.sample(textureSampler, in.tex_coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,17 +344,17 @@ fragment float4 cell_text_fragment(CellTextVertexOut in [[stage_in]],
|
||||
struct ImageVertexIn {
|
||||
// The grid coordinates (x, y) where x < columns and y < rows where
|
||||
// the image will be rendered. It will be rendered from the top left.
|
||||
float2 grid_pos [[attribute(1)]];
|
||||
float2 grid_pos [[attribute(0)]];
|
||||
|
||||
// Offset in pixels from the top-left of the cell to make the top-left
|
||||
// corner of the image.
|
||||
float2 cell_offset [[attribute(2)]];
|
||||
float2 cell_offset [[attribute(1)]];
|
||||
|
||||
// The source rectangle of the texture to sample from.
|
||||
float4 source_rect [[attribute(3)]];
|
||||
float4 source_rect [[attribute(2)]];
|
||||
|
||||
// The final width/height of the image in pixels.
|
||||
float2 dest_size [[attribute(4)]];
|
||||
float2 dest_size [[attribute(3)]];
|
||||
};
|
||||
|
||||
struct ImageVertexOut {
|
||||
@ -347,10 +362,12 @@ struct ImageVertexOut {
|
||||
float2 tex_coord;
|
||||
};
|
||||
|
||||
vertex ImageVertexOut image_vertex(unsigned int vid [[vertex_id]],
|
||||
ImageVertexIn input [[stage_in]],
|
||||
vertex ImageVertexOut image_vertex(
|
||||
uint vid [[vertex_id]],
|
||||
ImageVertexIn in [[stage_in]],
|
||||
texture2d<uint> image [[texture(0)]],
|
||||
constant Uniforms& uniforms [[buffer(1)]]) {
|
||||
constant Uniforms& uniforms [[buffer(1)]]
|
||||
) {
|
||||
// The size of the image in pixels
|
||||
float2 image_size = float2(image.get_width(), image.get_height());
|
||||
|
||||
@ -364,22 +381,22 @@ vertex ImageVertexOut image_vertex(unsigned int vid [[vertex_id]],
|
||||
// 1 = bot-right
|
||||
// 2 = bot-left
|
||||
// 3 = top-left
|
||||
float2 position;
|
||||
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
float2 corner;
|
||||
corner.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
corner.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
|
||||
// The texture coordinates start at our source x/y, then add the width/height
|
||||
// as enabled by our instance id, then normalize to [0, 1]
|
||||
float2 tex_coord = input.source_rect.xy;
|
||||
tex_coord += input.source_rect.zw * position;
|
||||
float2 tex_coord = in.source_rect.xy;
|
||||
tex_coord += in.source_rect.zw * corner;
|
||||
tex_coord /= image_size;
|
||||
|
||||
ImageVertexOut out;
|
||||
|
||||
// The position of our image starts at the top-left of the grid cell and
|
||||
// adds the source rect width/height components.
|
||||
float2 image_pos = (uniforms.cell_size * input.grid_pos) + input.cell_offset;
|
||||
image_pos += input.dest_size * position;
|
||||
float2 image_pos = (uniforms.cell_size * in.grid_pos) + in.cell_offset;
|
||||
image_pos += in.dest_size * corner;
|
||||
|
||||
out.position =
|
||||
uniforms.projection_matrix * float4(image_pos.x, image_pos.y, 0.0f, 1.0f);
|
||||
@ -387,8 +404,10 @@ vertex ImageVertexOut image_vertex(unsigned int vid [[vertex_id]],
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 image_fragment(ImageVertexOut in [[stage_in]],
|
||||
texture2d<uint> image [[texture(0)]]) {
|
||||
fragment float4 image_fragment(
|
||||
ImageVertexOut in [[stage_in]],
|
||||
texture2d<uint> image [[texture(0)]]
|
||||
) {
|
||||
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
||||
|
||||
// Ehhhhh our texture is in RGBA8Uint but our color attachment is
|
||||
@ -403,19 +422,3 @@ fragment float4 image_fragment(ImageVertexOut in [[stage_in]],
|
||||
return result;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Post Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Post Shader
|
||||
|
||||
struct PostVertexOut {
|
||||
float4 position [[position]];
|
||||
};
|
||||
|
||||
constant float2 post_pos[4] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}};
|
||||
|
||||
vertex PostVertexOut post_vertex(uint id [[vertex_id]]) {
|
||||
PostVertexOut out;
|
||||
out.position = float4(post_pos[id], 0, 1);
|
||||
return out;
|
||||
}
|
||||
|
Reference in New Issue
Block a user