mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
renderer/metal: dedicated cell fg shader
This commit is contained in:
@ -36,7 +36,6 @@ const Image = mtl_image.Image;
|
||||
const ImageMap = mtl_image.ImageMap;
|
||||
const Shaders = mtl_shaders.Shaders;
|
||||
|
||||
const CellBuffer = mtl_buffer.Buffer(mtl_shaders.Cell);
|
||||
const ImageBuffer = mtl_buffer.Buffer(mtl_shaders.Image);
|
||||
const InstanceBuffer = mtl_buffer.Buffer(u16);
|
||||
|
||||
@ -90,8 +89,8 @@ current_background_color: terminal.color.RGB,
|
||||
/// The current set of cells to render. This is rebuilt on every frame
|
||||
/// but we keep this around so that we don't reallocate. Each set of
|
||||
/// cells goes into a separate shader.
|
||||
cells_bg: std.ArrayListUnmanaged(mtl_shaders.Cell),
|
||||
cells: std.ArrayListUnmanaged(mtl_shaders.Cell),
|
||||
cells_bg: std.ArrayListUnmanaged(mtl_shaders.CellBg),
|
||||
cells: std.ArrayListUnmanaged(mtl_shaders.CellText),
|
||||
|
||||
/// The current GPU uniform values.
|
||||
uniforms: mtl_shaders.Uniforms,
|
||||
@ -205,8 +204,8 @@ pub const GPUState = struct {
|
||||
/// This is used to implement double/triple buffering.
|
||||
pub const FrameState = struct {
|
||||
uniforms: UniformBuffer,
|
||||
cells: CellBuffer,
|
||||
cells_bg: CellBuffer,
|
||||
cells: CellTextBuffer,
|
||||
cells_bg: CellBgBuffer,
|
||||
|
||||
greyscale: objc.Object, // MTLTexture
|
||||
greyscale_modified: usize = 0,
|
||||
@ -215,6 +214,8 @@ pub const FrameState = struct {
|
||||
|
||||
/// A buffer containing the uniform data.
|
||||
const UniformBuffer = mtl_buffer.Buffer(mtl_shaders.Uniforms);
|
||||
const CellBgBuffer = mtl_buffer.Buffer(mtl_shaders.CellBg);
|
||||
const CellTextBuffer = mtl_buffer.Buffer(mtl_shaders.CellText);
|
||||
|
||||
pub fn init(device: objc.Object) !FrameState {
|
||||
// Uniform buffer contains exactly 1 uniform struct. The
|
||||
@ -225,9 +226,9 @@ pub const FrameState = struct {
|
||||
|
||||
// Create the buffers for our vertex data. The preallocation size
|
||||
// is likely too small but our first frame update will resize it.
|
||||
var cells = try CellBuffer.init(device, 10 * 10);
|
||||
var cells = try CellTextBuffer.init(device, 10 * 10);
|
||||
errdefer cells.deinit();
|
||||
var cells_bg = try CellBuffer.init(device, 10 * 10);
|
||||
var cells_bg = try CellBgBuffer.init(device, 10 * 10);
|
||||
errdefer cells_bg.deinit();
|
||||
|
||||
// Initialize our textures for our font atlas.
|
||||
@ -1268,7 +1269,7 @@ fn drawCells(
|
||||
self: *Metal,
|
||||
encoder: objc.Object,
|
||||
frame: *const FrameState,
|
||||
buf: CellBuffer,
|
||||
buf: FrameState.CellTextBuffer,
|
||||
len: usize,
|
||||
) !void {
|
||||
// This triggers an assertion in the Metal API if we try to draw
|
||||
@ -1279,7 +1280,7 @@ fn drawCells(
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setRenderPipelineState:"),
|
||||
.{self.shaders.cell_pipeline.value},
|
||||
.{self.shaders.cell_text_pipeline.value},
|
||||
);
|
||||
|
||||
// Set our buffers
|
||||
@ -1694,7 +1695,7 @@ fn rebuildCells(
|
||||
// This is the cell that has [mode == .fg] and is underneath our cursor.
|
||||
// We keep track of it so that we can invert the colors so the character
|
||||
// remains visible.
|
||||
var cursor_cell: ?mtl_shaders.Cell = null;
|
||||
var cursor_cell: ?mtl_shaders.CellText = null;
|
||||
|
||||
// Build each cell
|
||||
var row_it = screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
|
||||
@ -1847,12 +1848,6 @@ fn rebuildCells(
|
||||
self.cells.appendAssumeCapacity(cell.*);
|
||||
}
|
||||
}
|
||||
|
||||
// Some debug mode safety checks
|
||||
if (std.debug.runtime_safety) {
|
||||
for (self.cells_bg.items) |cell| assert(cell.mode == .bg);
|
||||
for (self.cells.items) |cell| assert(cell.mode != .bg);
|
||||
}
|
||||
}
|
||||
|
||||
fn updateCell(
|
||||
@ -1964,11 +1959,10 @@ fn updateCell(
|
||||
};
|
||||
|
||||
self.cells_bg.appendAssumeCapacity(.{
|
||||
.mode = .bg,
|
||||
.mode = .rgb,
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = cell.gridWidth(),
|
||||
.color = .{ rgb.r, rgb.g, rgb.b, bg_alpha },
|
||||
.bg_color = .{ 0, 0, 0, 0 },
|
||||
});
|
||||
|
||||
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
|
||||
@ -1992,7 +1986,7 @@ fn updateCell(
|
||||
},
|
||||
);
|
||||
|
||||
const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
|
||||
const mode: mtl_shaders.CellText.Mode = switch (try fgMode(
|
||||
render.presentation,
|
||||
cell_pin,
|
||||
)) {
|
||||
@ -2080,7 +2074,7 @@ fn addCursor(
|
||||
self: *Metal,
|
||||
screen: *terminal.Screen,
|
||||
cursor_style: renderer.CursorStyle,
|
||||
) ?*const mtl_shaders.Cell {
|
||||
) ?*const mtl_shaders.CellText {
|
||||
// Add the cursor. We render the cursor over the wide character if
|
||||
// we're on the wide characer tail.
|
||||
const wide, const x = cell: {
|
||||
@ -2166,11 +2160,10 @@ fn addPreeditCell(
|
||||
|
||||
// Add our opaque background cell
|
||||
self.cells_bg.appendAssumeCapacity(.{
|
||||
.mode = .bg,
|
||||
.mode = .rgb,
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = if (cp.wide) 2 else 1,
|
||||
.color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
.bg_color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
});
|
||||
|
||||
// Add our text
|
||||
|
@ -16,7 +16,7 @@ pub const Shaders = struct {
|
||||
/// The cell shader is the shader used to render the terminal cells.
|
||||
/// It is a single shader that is used for both the background and
|
||||
/// foreground.
|
||||
cell_pipeline: objc.Object,
|
||||
cell_text_pipeline: objc.Object,
|
||||
|
||||
/// The cell background shader is the shader used to render the
|
||||
/// background of terminal cells.
|
||||
@ -44,8 +44,8 @@ pub const Shaders = struct {
|
||||
const library = try initLibrary(device);
|
||||
errdefer library.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
const cell_pipeline = try initCellPipeline(device, library);
|
||||
errdefer cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
const cell_text_pipeline = try initCellTextPipeline(device, library);
|
||||
errdefer cell_text_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
const cell_bg_pipeline = try initCellBgPipeline(device, library);
|
||||
errdefer cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
@ -72,7 +72,7 @@ pub const Shaders = struct {
|
||||
|
||||
return .{
|
||||
.library = library,
|
||||
.cell_pipeline = cell_pipeline,
|
||||
.cell_text_pipeline = cell_text_pipeline,
|
||||
.cell_bg_pipeline = cell_bg_pipeline,
|
||||
.image_pipeline = image_pipeline,
|
||||
.post_pipelines = post_pipelines,
|
||||
@ -81,7 +81,7 @@ pub const Shaders = struct {
|
||||
|
||||
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
||||
// Release our primary shaders
|
||||
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.cell_text_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.library.msgSend(void, objc.sel("release"), .{});
|
||||
@ -96,25 +96,6 @@ pub const Shaders = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// This is a single parameter for the terminal cell shader.
|
||||
pub const Cell = extern struct {
|
||||
mode: Mode,
|
||||
grid_pos: [2]f32,
|
||||
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,
|
||||
cell_width: u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
bg = 1,
|
||||
fg = 2,
|
||||
fg_constrained = 3,
|
||||
fg_color = 7,
|
||||
};
|
||||
};
|
||||
|
||||
/// Single parameter for the image shader. See shader for field details.
|
||||
pub const Image = extern struct {
|
||||
grid_pos: [2]f32,
|
||||
@ -303,12 +284,31 @@ fn initPostPipeline(
|
||||
return pipeline_state;
|
||||
}
|
||||
|
||||
/// This is a single parameter for the terminal cell shader.
|
||||
pub const CellText = extern struct {
|
||||
mode: Mode,
|
||||
grid_pos: [2]f32,
|
||||
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,
|
||||
cell_width: u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
bg = 1,
|
||||
fg = 2,
|
||||
fg_constrained = 3,
|
||||
fg_color = 7,
|
||||
};
|
||||
};
|
||||
|
||||
/// Initialize the cell render pipeline for our shader library.
|
||||
fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
fn initCellTextPipeline(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(
|
||||
"uber_vertex",
|
||||
"cell_text_vertex",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
@ -319,7 +319,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
};
|
||||
const func_frag = func_frag: {
|
||||
const str = try macos.foundation.String.createWithBytes(
|
||||
"uber_fragment",
|
||||
"cell_text_fragment",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
@ -353,7 +353,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "mode")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "mode")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -364,7 +364,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "grid_pos")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -375,7 +375,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_pos")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -386,7 +386,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uint2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_size")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_size")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -397,7 +397,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.int2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "glyph_offset")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "glyph_offset")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -408,7 +408,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "color")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -419,7 +419,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "bg_color")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "bg_color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -430,7 +430,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "cell_width")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellText, "cell_width")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
|
||||
@ -445,7 +445,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
|
||||
// Access each Cell per instance, not per vertex.
|
||||
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(Cell)));
|
||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(CellText)));
|
||||
}
|
||||
|
||||
break :vertex_desc desc;
|
||||
@ -506,8 +506,8 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
pub const CellBg = extern struct {
|
||||
mode: Mode,
|
||||
grid_pos: [2]f32,
|
||||
cell_width: u8,
|
||||
color: [4]u8,
|
||||
cell_width: u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
rgb = 1,
|
||||
@ -575,7 +575,7 @@ fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "grid_pos")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -586,7 +586,7 @@ fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "cell_width")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "cell_width")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
@ -597,7 +597,7 @@ fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "color")));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
|
||||
@ -612,7 +612,7 @@ fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
|
||||
// Access each Cell per instance, not per vertex.
|
||||
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(Cell)));
|
||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(CellBg)));
|
||||
}
|
||||
|
||||
break :vertex_desc desc;
|
||||
@ -840,11 +840,20 @@ fn checkError(err_: ?*anyopaque) !void {
|
||||
// on macOS 12 or Apple Silicon macOS 13.
|
||||
//
|
||||
// To be safe, we put this test in here.
|
||||
test "Cell offsets" {
|
||||
test "CellText offsets" {
|
||||
const testing = std.testing;
|
||||
const alignment = @alignOf(Cell);
|
||||
inline for (@typeInfo(Cell).Struct.fields) |field| {
|
||||
const offset = @offsetOf(Cell, field.name);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +1,11 @@
|
||||
using namespace metal;
|
||||
|
||||
// The possible modes that a shader can take.
|
||||
enum Mode : uint8_t {
|
||||
MODE_BG = 1u,
|
||||
MODE_FG = 2u,
|
||||
MODE_FG_CONSTRAINED = 3u,
|
||||
MODE_FG_COLOR = 7u,
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
float4x4 projection_matrix;
|
||||
float2 cell_size;
|
||||
float min_contrast;
|
||||
};
|
||||
|
||||
struct VertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
float2 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. 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(5)]];
|
||||
|
||||
// The fields below are present only when rendering text (fg mode)
|
||||
|
||||
// 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)]];
|
||||
|
||||
// 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)]];
|
||||
};
|
||||
|
||||
struct VertexOut {
|
||||
float4 position [[position]];
|
||||
float2 cell_size;
|
||||
uint8_t mode;
|
||||
float4 color;
|
||||
float2 tex_coord;
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Color Functions
|
||||
//-------------------------------------------------------------------
|
||||
@ -102,142 +55,13 @@ float4 contrasted_color(float min, float4 fg, float4 bg) {
|
||||
return fg;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Terminal Grid Cell Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Terminal Grid Cell Shader
|
||||
|
||||
vertex VertexOut uber_vertex(unsigned int vid [[vertex_id]],
|
||||
VertexIn 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 * 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;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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;
|
||||
|
||||
VertexOut out;
|
||||
out.mode = input.mode;
|
||||
out.cell_size = uniforms.cell_size;
|
||||
out.color = float4(input.color) / 255.0f;
|
||||
switch (input.mode) {
|
||||
case MODE_BG:
|
||||
// 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;
|
||||
|
||||
out.position = uniforms.projection_matrix *
|
||||
float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||
break;
|
||||
|
||||
case MODE_FG:
|
||||
case MODE_FG_CONSTRAINED:
|
||||
case MODE_FG_COLOR: {
|
||||
float2 glyph_size = float2(input.glyph_size);
|
||||
float2 glyph_offset = float2(input.glyph_offset);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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_FG_CONSTRAINED || input.mode == MODE_FG_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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
|
||||
// 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
|
||||
// with the background.
|
||||
if (uniforms.min_contrast > 1.0f && input.mode == MODE_FG) {
|
||||
float4 bg_color = float4(input.bg_color) / 255.0f;
|
||||
out.color =
|
||||
contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 uber_fragment(VertexOut in [[stage_in]],
|
||||
texture2d<float> textureGreyscale [[texture(0)]],
|
||||
texture2d<float> textureColor [[texture(1)]]) {
|
||||
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
||||
|
||||
switch (in.mode) {
|
||||
case MODE_BG:
|
||||
return in.color;
|
||||
|
||||
case MODE_FG_CONSTRAINED:
|
||||
case MODE_FG: {
|
||||
// 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;
|
||||
premult = premult * a;
|
||||
return premult;
|
||||
}
|
||||
|
||||
case MODE_FG_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Cell Background Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Cell BG Shader
|
||||
|
||||
// The possible modes that a cell bg entry can take.
|
||||
enum CellbgMode : uint8_t {
|
||||
enum CellBgMode : uint8_t {
|
||||
MODE_RGB = 1u,
|
||||
};
|
||||
|
||||
@ -248,12 +72,12 @@ struct CellBgVertexIn {
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
float2 grid_pos [[attribute(1)]];
|
||||
|
||||
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||
uint8_t cell_width [[attribute(2)]];
|
||||
|
||||
// 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 {
|
||||
@ -303,6 +127,158 @@ fragment float4 cell_bg_fragment(CellBgVertexOut in [[stage_in]]) {
|
||||
return in.color;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Cell Text Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Cell Text Shader
|
||||
|
||||
// The possible modes that a cell fg entry can take.
|
||||
enum CellTextMode : uint8_t {
|
||||
MODE_TEXT = 2u,
|
||||
MODE_TEXT_CONSTRAINED = 3u,
|
||||
MODE_TEXT_COLOR = 7u,
|
||||
};
|
||||
|
||||
struct CellTextVertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
float2 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)]];
|
||||
|
||||
// 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)]];
|
||||
};
|
||||
|
||||
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 * 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;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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;
|
||||
|
||||
CellTextVertexOut out;
|
||||
out.mode = input.mode;
|
||||
out.cell_size = uniforms.cell_size;
|
||||
out.color = float4(input.color) / 255.0f;
|
||||
|
||||
float2 glyph_size = float2(input.glyph_size);
|
||||
float2 glyph_offset = float2(input.glyph_offset);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
|
||||
// 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
|
||||
// with the background.
|
||||
if (uniforms.min_contrast > 1.0f && input.mode == MODE_TEXT) {
|
||||
float4 bg_color = float4(input.bg_color) / 255.0f;
|
||||
out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
switch (in.mode) {
|
||||
case MODE_TEXT_CONSTRAINED:
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
// Image Shader
|
||||
//-------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user