mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
renderer: metal shaders rework
- Significant changes to optimize memory usage. - Adjusted formatting of the metal shader code to improve readability. - Normalized naming conventions in shader code. - Abstracted repetitive code for attribute descriptors to a helper function.
This commit is contained in:
@ -1084,7 +1084,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 +1179,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, self.cells.size.columns * self.cells.size.rows);
|
||||
|
||||
// Then draw images under text
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
||||
@ -1519,25 +1519,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 +1568,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:"),
|
||||
@ -2411,7 +2413,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,20 +2442,16 @@ 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 },
|
||||
});
|
||||
self.cells.bg_cells[coord.y * self.cells.size.columns + coord.x] = .{
|
||||
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 (cell.gridWidth() > 1 and coord.x < self.cells.size.columns - 1) {
|
||||
self.cells.bg_cells[coord.y * self.cells.size.columns + coord.x + 1] = .{
|
||||
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: {
|
||||
@ -2487,14 +2485,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 +2521,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 +2546,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 +2608,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 +2644,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.bg_cells[coord.y * self.cells.size.columns + coord.x] = .{
|
||||
bg.r, bg.g, bg.b, 255,
|
||||
};
|
||||
if (cp.wide and coord.x < self.cells.size.columns - 1) {
|
||||
self.cells.bg_cells[coord.y * self.cells.size.columns + 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,7 @@ 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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) = .{},
|
||||
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 +101,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 +114,10 @@ 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 = size.columns * size.rows;
|
||||
|
||||
const bg_cells = (try alloc.alloc(mtl_shaders.CellBg, cell_count))[0..cell_count];
|
||||
errdefer alloc.free(bg_cells);
|
||||
|
||||
// The foreground lists can hold 3 types of items:
|
||||
// - Glyphs
|
||||
@ -154,10 +134,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 +150,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();
|
||||
}
|
||||
|
||||
@ -197,7 +177,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 +193,10 @@ 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();
|
||||
for (self.bg_cells[y * self.size.columns ..][0..self.size.columns]) |*cell| {
|
||||
cell.* = .{ 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.
|
||||
|
@ -246,7 +246,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 +307,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 +322,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 +372,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 +443,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 +494,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 +609,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 +678,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 +723,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));
|
||||
}
|
||||
}
|
||||
|
@ -64,93 +64,75 @@ 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((in.position.xy - uniforms.grid_padding.wx) / uniforms.cell_size);
|
||||
|
||||
// Clamp x position, extends edge bg colors in to padding on sides.
|
||||
grid_pos.x = clamp(grid_pos.x, 0, uniforms.grid_size.x - 1);
|
||||
|
||||
// Clamp y position if we should extend, otherwise discard if out of bounds.
|
||||
if (grid_pos.y < 0) {
|
||||
if (uniforms.padding_extend_top) {
|
||||
grid_pos.y = 0;
|
||||
} else {
|
||||
return float4(0.0);
|
||||
}
|
||||
}
|
||||
if (grid_pos.y > uniforms.grid_size.y - 1) {
|
||||
if (uniforms.padding_extend_bottom) {
|
||||
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 +150,43 @@ enum CellTextMode : uint8_t {
|
||||
};
|
||||
|
||||
struct CellTextVertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
// The position of the glyph in the texture (x, y)
|
||||
uint2 glyph_pos [[attribute(0)]];
|
||||
|
||||
// The size of the glyph in the texture (w, h)
|
||||
uint2 glyph_size [[attribute(1)]];
|
||||
|
||||
// The left and top bearings for the glyph (x, y)
|
||||
int2 bearings [[attribute(2)]];
|
||||
|
||||
// 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)]];
|
||||
ushort2 grid_pos [[attribute(3)]];
|
||||
|
||||
// The color of the rendered text glyph.
|
||||
uchar4 color [[attribute(5)]];
|
||||
uchar4 color [[attribute(4)]];
|
||||
|
||||
// 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 mode for this cell.
|
||||
uint8_t mode [[attribute(5)]];
|
||||
|
||||
// The position of the glyph in the texture (x,y)
|
||||
uint2 glyph_pos [[attribute(2)]];
|
||||
|
||||
// The size of the glyph in the texture (w,h)
|
||||
uint2 glyph_size [[attribute(3)]];
|
||||
|
||||
// The left and top bearings for the glyph (x,y)
|
||||
int2 glyph_offset [[attribute(4)]];
|
||||
// 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)]]) {
|
||||
// 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;
|
||||
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(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 +198,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 +268,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 +302,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 +327,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 +345,12 @@ struct ImageVertexOut {
|
||||
float2 tex_coord;
|
||||
};
|
||||
|
||||
vertex ImageVertexOut image_vertex(unsigned int vid [[vertex_id]],
|
||||
ImageVertexIn input [[stage_in]],
|
||||
texture2d<uint> image [[texture(0)]],
|
||||
constant Uniforms& uniforms [[buffer(1)]]) {
|
||||
vertex ImageVertexOut image_vertex(
|
||||
uint vid [[vertex_id]],
|
||||
ImageVertexIn in [[stage_in]],
|
||||
texture2d<uint> image [[texture(0)]],
|
||||
constant Uniforms& uniforms [[buffer(1)]]
|
||||
) {
|
||||
// The size of the image in pixels
|
||||
float2 image_size = float2(image.get_width(), image.get_height());
|
||||
|
||||
@ -364,22 +364,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 +387,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 +405,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