Merge pull request #88 from mitchellh/sprite-cursor

Use procedurally generated sprites for cursors rather than shader logic
This commit is contained in:
Mitchell Hashimoto
2023-03-04 15:16:03 -08:00
committed by GitHub
9 changed files with 91 additions and 183 deletions

View File

@ -20,6 +20,10 @@ pub const Sprite = enum(u32) {
underline_dashed, underline_dashed,
underline_curly, underline_curly,
cursor_rect,
cursor_hollow_rect,
cursor_bar,
// Note: we don't currently put the box drawing glyphs in here because // Note: we don't currently put the box drawing glyphs in here because
// there are a LOT and I'm lazy. What I want to do is spend more time // there are a LOT and I'm lazy. What I want to do is spend more time
// studying the patterns to see if we can programmatically build our // studying the patterns to see if we can programmatically build our

View File

@ -16,6 +16,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const font = @import("../main.zig"); const font = @import("../main.zig");
const Sprite = @import("../sprite.zig").Sprite;
const log = std.log.scoped(.box_font); const log = std.log.scoped(.box_font);
@ -30,6 +31,7 @@ thickness: u32,
/// The thickness of a line. /// The thickness of a line.
const Thickness = enum { const Thickness = enum {
super_light,
light, light,
heavy, heavy,
@ -38,6 +40,7 @@ const Thickness = enum {
/// to be in pixels. /// to be in pixels.
fn height(self: Thickness, base: u32) u32 { fn height(self: Thickness, base: u32) u32 {
return switch (self) { return switch (self) {
.super_light => @max(base / 2, 1),
.light => base, .light => base,
.heavy => base * 2, .heavy => base * 2,
}; };
@ -303,6 +306,12 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void
0x1fb8a => self.draw_right_three_quarters_block(canvas), 0x1fb8a => self.draw_right_three_quarters_block(canvas),
0x1fb8b => self.draw_right_seven_eighths_block(canvas), 0x1fb8b => self.draw_right_seven_eighths_block(canvas),
// Not official box characters but special characters we hide
// in the high bits of a unicode codepoint.
@enumToInt(Sprite.cursor_rect) => self.draw_cursor_rect(canvas),
@enumToInt(Sprite.cursor_hollow_rect) => self.draw_cursor_hollow_rect(canvas),
@enumToInt(Sprite.cursor_bar) => self.draw_cursor_bar(canvas),
else => return error.InvalidCodepoint, else => return error.InvalidCodepoint,
} }
} }
@ -2532,6 +2541,27 @@ fn draw_dash_vertical(
self.vline(canvas, y[3], y[3] + h[3], (self.width - thick_px) / 2, thick_px); self.vline(canvas, y[3], y[3] + h[3], (self.width - thick_px) / 2, thick_px);
} }
fn draw_cursor_rect(self: Box, canvas: *font.sprite.Canvas) void {
const thick_px = Thickness.super_light.height(self.thickness);
self.rect(canvas, 0, 0, self.width - thick_px, self.height - thick_px);
}
fn draw_cursor_hollow_rect(self: Box, canvas: *font.sprite.Canvas) void {
const thick_px = Thickness.super_light.height(self.thickness);
self.vline(canvas, 0, self.height, 0, thick_px);
self.vline(canvas, 0, self.height, self.width - thick_px, thick_px);
self.hline(canvas, 0, self.width, 0, thick_px);
self.hline(canvas, 0, self.width, self.height - thick_px, thick_px);
}
fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void {
const thick_px = Thickness.light.height(self.thickness);
self.vline(canvas, 0, self.height - thick_px, 0, thick_px);
}
fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
const thick_px = thickness.height(self.thickness); const thick_px = thickness.height(self.thickness);
self.vline(canvas, 0, self.height, (self.width - thick_px) / 2, thick_px); self.vline(canvas, 0, self.height, (self.width - thick_px) / 2, thick_px);

View File

@ -95,6 +95,11 @@ const Kind = enum {
.underline_dashed, .underline_dashed,
.underline_curly, .underline_curly,
=> .underline, => .underline,
.cursor_rect,
.cursor_hollow_rect,
.cursor_bar,
=> .box,
}, },
// Box fonts // Box fonts

View File

@ -71,6 +71,7 @@ const Draw = struct {
.underline_dotted => self.drawDotted(canvas), .underline_dotted => self.drawDotted(canvas),
.underline_dashed => self.drawDashed(canvas), .underline_dashed => self.drawDashed(canvas),
.underline_curly => self.drawCurly(canvas), .underline_curly => self.drawCurly(canvas),
else => unreachable,
} }
} }

View File

@ -114,18 +114,7 @@ const GPUCellMode = enum(u8) {
bg = 1, bg = 1,
fg = 2, fg = 2,
fg_color = 7, fg_color = 7,
cursor_rect = 3,
cursor_rect_hollow = 4,
cursor_bar = 5,
strikethrough = 8, strikethrough = 8,
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
return switch (cursor) {
.box => .cursor_rect,
.box_hollow => .cursor_rect_hollow,
.bar => .cursor_bar,
};
}
}; };
/// Returns the hints that we want for this /// Returns the hints that we want for this
@ -1005,14 +994,33 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
.b = 0xFF, .b = 0xFF,
}; };
const sprite: font.Sprite = switch (self.cursor_style) {
.box => .cursor_rect,
.box_hollow => .cursor_hollow_rect,
.bar => .cursor_bar,
};
const glyph = self.font_group.renderGlyph(
self.alloc,
font.sprite_index,
@enumToInt(sprite),
null,
) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err});
return;
};
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = GPUCellMode.fromCursor(self.cursor_style), .mode = .fg,
.grid_pos = .{ .grid_pos = .{
@intToFloat(f32, screen.cursor.x), @intToFloat(f32, screen.cursor.x),
@intToFloat(f32, screen.cursor.y), @intToFloat(f32, screen.cursor.y),
}, },
.cell_width = if (cell.attrs.wide) 2 else 1, .cell_width = if (cell.attrs.wide) 2 else 1,
.color = .{ color.r, color.g, color.b, 0xFF }, .color = .{ color.r, color.g, color.b, 0xFF },
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
.glyph_size = .{ glyph.width, glyph.height },
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
}); });
} }

View File

@ -218,22 +218,11 @@ const GPUCellMode = enum(u8) {
bg = 1, bg = 1,
fg = 2, fg = 2,
fg_color = 7, fg_color = 7,
cursor_rect = 3,
cursor_rect_hollow = 4,
cursor_bar = 5,
strikethrough = 8, strikethrough = 8,
// Non-exhaustive because masks change it // Non-exhaustive because masks change it
_, _,
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
return switch (cursor) {
.box => .cursor_rect,
.box_hollow => .cursor_rect_hollow,
.bar => .cursor_bar,
};
}
/// Apply a mask to the mode. /// Apply a mask to the mode.
pub fn mask(self: GPUCellMode, m: GPUCellMode) GPUCellMode { pub fn mask(self: GPUCellMode, m: GPUCellMode) GPUCellMode {
return @intToEnum( return @intToEnum(
@ -961,19 +950,41 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
.b = 0xFF, .b = 0xFF,
}; };
const sprite: font.Sprite = switch (self.cursor_style) {
.box => .cursor_rect,
.box_hollow => .cursor_hollow_rect,
.bar => .cursor_bar,
};
const glyph = self.font_group.renderGlyph(
self.alloc,
font.sprite_index,
@enumToInt(sprite),
null,
) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err});
return;
};
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = GPUCellMode.fromCursor(self.cursor_style), .mode = .fg,
.grid_col = @intCast(u16, screen.cursor.x), .grid_col = @intCast(u16, screen.cursor.x),
.grid_row = @intCast(u16, screen.cursor.y), .grid_row = @intCast(u16, screen.cursor.y),
.grid_width = if (cell.attrs.wide) 2 else 1, .grid_width = if (cell.attrs.wide) 2 else 1,
.fg_r = 0, .fg_r = color.r,
.fg_g = 0, .fg_g = color.g,
.fg_b = 0, .fg_b = color.b,
.fg_a = 0, .fg_a = 255,
.bg_r = color.r, .bg_r = 0,
.bg_g = color.g, .bg_g = 0,
.bg_b = color.b, .bg_b = 0,
.bg_a = 255, .bg_a = 0,
.glyph_x = glyph.atlas_x,
.glyph_y = glyph.atlas_y,
.glyph_width = glyph.width,
.glyph_height = glyph.height,
.glyph_offset_x = glyph.offset_x,
.glyph_offset_y = glyph.offset_y,
}); });
} }

View File

@ -27,9 +27,6 @@ uniform vec2 cell_size;
const uint MODE_BG = 1u; const uint MODE_BG = 1u;
const uint MODE_FG = 2u; const uint MODE_FG = 2u;
const uint MODE_FG_COLOR = 7u; const uint MODE_FG_COLOR = 7u;
const uint MODE_CURSOR_RECT = 3u;
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
const uint MODE_CURSOR_BAR = 5u;
const uint MODE_STRIKETHROUGH = 8u; const uint MODE_STRIKETHROUGH = 8u;
void main() { void main() {
@ -49,50 +46,6 @@ void main() {
out_FragColor = texture(text_color, glyph_tex_coords); out_FragColor = texture(text_color, glyph_tex_coords);
break; break;
case MODE_CURSOR_RECT:
out_FragColor = color;
break;
case MODE_CURSOR_RECT_HOLLOW:
// Okay so yeah this is probably horrendously slow and a shader
// should never do this, but we only ever render a cursor for ONE
// rectangle so we take the slowdown for that one.
// Default to no color.
out_FragColor = vec4(0., 0., 0., 0.);
// We subtracted one from cell size because our coordinates start at 0.
// So a width of 50 means max pixel of 49.
vec2 cell_size_coords = cell_size - 1;
// Apply padding
vec2 padding = vec2(1.,1.);
cell_size_coords = cell_size_coords - (padding * 2);
vec2 screen_cell_pos_padded = screen_cell_pos + padding;
// Convert our frag coord to offset of this cell. We have to subtract
// 0.5 because the frag coord is in center pixels.
vec2 cell_frag_coord = gl_FragCoord.xy - screen_cell_pos_padded - 0.5;
// If the frag coords are in the bounds, then we color it.
const float eps = 0.1;
if (cell_frag_coord.x >= 0 && cell_frag_coord.y >= 0 &&
cell_frag_coord.x <= cell_size_coords.x &&
cell_frag_coord.y <= cell_size_coords.y) {
if (abs(cell_frag_coord.x) < eps ||
abs(cell_frag_coord.x - cell_size_coords.x) < eps ||
abs(cell_frag_coord.y) < eps ||
abs(cell_frag_coord.y - cell_size_coords.y) < eps) {
out_FragColor = color;
}
}
break;
case MODE_CURSOR_BAR:
out_FragColor = color;
break;
case MODE_STRIKETHROUGH: case MODE_STRIKETHROUGH:
out_FragColor = color; out_FragColor = color;
break; break;

View File

@ -5,9 +5,6 @@ enum Mode : uint8_t {
MODE_BG = 1u, MODE_BG = 1u,
MODE_FG = 2u, MODE_FG = 2u,
MODE_FG_COLOR = 7u, MODE_FG_COLOR = 7u,
MODE_CURSOR_RECT = 3u,
MODE_CURSOR_RECT_HOLLOW = 4u,
MODE_CURSOR_BAR = 5u,
MODE_STRIKETHROUGH = 8u, MODE_STRIKETHROUGH = 8u,
}; };
@ -124,33 +121,6 @@ vertex VertexOut uber_vertex(
break; break;
} }
case MODE_CURSOR_RECT:
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + cell_size_scaled * position;
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
break;
case MODE_CURSOR_RECT_HOLLOW:
// Top-left position of this cell is needed for the hollow rect.
out.tex_coord = cell_pos;
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + cell_size_scaled * position;
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
break;
case MODE_CURSOR_BAR: {
// Make the bar a smaller version of our cell
float2 bar_size = float2(uniforms.cell_size.x * 0.2, uniforms.cell_size.y);
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + bar_size * position;
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
break;
}
case MODE_STRIKETHROUGH: { case MODE_STRIKETHROUGH: {
// Strikethrough Y value is just our thickness // Strikethrough Y value is just our thickness
float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness); float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness);
@ -201,47 +171,6 @@ fragment float4 uber_fragment(
return textureColor.sample(textureSampler, coord); return textureColor.sample(textureSampler, coord);
} }
case MODE_CURSOR_RECT:
return in.color;
case MODE_CURSOR_RECT_HOLLOW: {
// Okay so yeah this is probably horrendously slow and a shader
// should never do this, but we only ever render a cursor for ONE
// rectangle so we take the slowdown for that one.
// We subtracted one from cell size because our coordinates start at 0.
// So a width of 50 means max pixel of 49.
float2 cell_size_coords = in.cell_size - 1;
// Apply padding
float2 padding = float2(1.0f, 1.0f);
cell_size_coords = cell_size_coords - (padding * 2);
float2 screen_cell_pos_padded = in.tex_coord + padding;
// Convert our frag coord to offset of this cell. We have to subtract
// 0.5 because the frag coord is in center pixels.
float2 cell_frag_coord = in.position.xy - screen_cell_pos_padded - 0.5;
// If the frag coords are in the bounds, then we color it.
const float eps = 0.1;
if (cell_frag_coord.x >= 0 && cell_frag_coord.y >= 0 &&
cell_frag_coord.x <= cell_size_coords.x &&
cell_frag_coord.y <= cell_size_coords.y) {
if (abs(cell_frag_coord.x) < eps ||
abs(cell_frag_coord.x - cell_size_coords.x) < eps ||
abs(cell_frag_coord.y) < eps ||
abs(cell_frag_coord.y - cell_size_coords.y) < eps) {
return in.color;
}
}
// Default to no color.
return float4(0.0f);
}
case MODE_CURSOR_BAR:
return in.color;
case MODE_STRIKETHROUGH: case MODE_STRIKETHROUGH:
return in.color; return in.color;
} }

View File

@ -7,9 +7,6 @@
const uint MODE_BG = 1u; const uint MODE_BG = 1u;
const uint MODE_FG = 2u; const uint MODE_FG = 2u;
const uint MODE_FG_COLOR = 7u; const uint MODE_FG_COLOR = 7u;
const uint MODE_CURSOR_RECT = 3u;
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
const uint MODE_CURSOR_BAR = 5u;
const uint MODE_STRIKETHROUGH = 8u; const uint MODE_STRIKETHROUGH = 8u;
// The grid coordinates (x, y) where x < columns and y < rows // The grid coordinates (x, y) where x < columns and y < rows
@ -167,36 +164,6 @@ void main() {
color = fg_color_in / 255.; color = fg_color_in / 255.;
break; break;
case MODE_CURSOR_RECT:
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + cell_size_scaled * position;
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
color = bg_color_in / 255.0;
break;
case MODE_CURSOR_RECT_HOLLOW:
// Top-left position of this cell is needed for the hollow rect.
screen_cell_pos = cell_pos;
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + cell_size_scaled * position;
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
color = bg_color_in / 255.0;
break;
case MODE_CURSOR_BAR:
// Make the bar a smaller version of our cell
vec2 bar_size = vec2(cell_size.x * 0.2, cell_size.y);
// Same as background since we're taking up the whole cell.
cell_pos = cell_pos + bar_size * position;
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
color = bg_color_in / 255.0;
break;
case MODE_STRIKETHROUGH: case MODE_STRIKETHROUGH:
// Strikethrough Y value is just our thickness // Strikethrough Y value is just our thickness
vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness); vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);