diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 6d5bcbaf6..632eab3fe 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -205,24 +205,6 @@ pub fn isCovering(cp: u21) bool { }; } -pub const FgMode = enum { - /// Normal non-colored text rendering. The text can leave the cell - /// size if it is larger than the cell to allow for ligatures. - normal, - - /// Colored text rendering, specifically Emoji. - color, - - /// Similar to normal but the text must be constrained to the cell - /// size. If a glyph is larger than the cell then it must be resized - /// to fit. - constrained, - - /// Similar to normal, but the text consists of Powerline glyphs and is - /// optionally exempt from padding color extension and minimum contrast requirements. - powerline, -}; - /// Returns the appropriate `constraint_width` for /// the provided cell when rendering its glyph(s). pub fn constraintWidth(cell_pin: terminal.Pin) u2 { @@ -277,85 +259,10 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 { return 1; } -/// Returns the appropriate foreground mode for the given cell. This is -/// meant to be called from the typical updateCell function within a -/// renderer. -pub fn fgMode( - presentation: font.Presentation, - cell_pin: terminal.Pin, -) FgMode { - return switch (presentation) { - // Emoji is always full size and color. - .emoji => .color, - - // If it is text it is slightly more complex. If we are a codepoint - // in the private use area and we are at the end or the next cell - // is not empty, we need to constrain rendering. - // - // We do this specifically so that Nerd Fonts can render their - // icons without overlapping with subsequent characters. But if - // the subsequent character is empty, then we allow it to use - // the full glyph size. See #1071. - .text => text: { - const cell = cell_pin.rowAndCell().cell; - const cp = cell.codepoint(); - - if (!ziglyph.general_category.isPrivateUse(cp) and - !ziglyph.blocks.isDingbats(cp)) - { - break :text .normal; - } - - // Special-case Powerline glyphs. They exhibit box drawing behavior - // and should not be constrained. They have their own special category - // though because they're used for other logic (i.e. disabling - // min contrast). - if (isPowerline(cp)) { - break :text .powerline; - } - - // If we are at the end of the screen its definitely constrained - if (cell_pin.x == cell_pin.node.data.size.cols - 1) break :text .constrained; - - // If we have a previous cell and it was PUA then we need to - // also constrain. This is so that multiple PUA glyphs align. - // As an exception, we ignore powerline glyphs since they are - // used for box drawing and we consider them whitespace. - if (cell_pin.x > 0) prev: { - const prev_cp = prev_cp: { - var copy = cell_pin; - copy.x -= 1; - const prev_cell = copy.rowAndCell().cell; - break :prev_cp prev_cell.codepoint(); - }; - - // Powerline is whitespace - if (isPowerline(prev_cp)) break :prev; - - if (ziglyph.general_category.isPrivateUse(prev_cp)) { - break :text .constrained; - } - } - - // If the next cell is empty, then we allow it to use the - // full glyph size. - const next_cp = next_cp: { - var copy = cell_pin; - copy.x += 1; - const next_cell = copy.rowAndCell().cell; - break :next_cp next_cell.codepoint(); - }; - if (next_cp == 0 or - isSpace(next_cp) or - isPowerline(next_cp)) - { - break :text .normal; - } - - // Must be constrained - break :text .constrained; - }, - }; +/// Whether min contrast should be disabled for a given glyph. +pub fn noMinContrast(cp: u21) bool { + // TODO: We should disable for all box drawing type characters. + return isPowerline(cp); } // Some general spaces, others intentionally kept diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 539b478c0..3b9879019 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -13,7 +13,7 @@ const math = @import("../math.zig"); const Surface = @import("../Surface.zig"); const link = @import("link.zig"); const cellpkg = @import("cell.zig"); -const fgMode = cellpkg.fgMode; +const noMinContrast = cellpkg.noMinContrast; const constraintWidth = cellpkg.constraintWidth; const isCovering = cellpkg.isCovering; const imagepkg = @import("image.zig"); @@ -2933,9 +2933,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .underline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2965,9 +2964,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .overline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2997,9 +2995,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .strikethrough, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3024,6 +3021,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { const rac = cell_pin.rowAndCell(); const cell = rac.cell; + const cp = cell.codepoint(); + // Render const render = try self.font_grid.renderGlyph( self.alloc, @@ -3034,7 +3033,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .thicken = self.config.font_thicken, .thicken_strength = self.config.font_thicken_strength, .cell_width = cell.gridWidth(), - .constraint = getConstraint(cell.codepoint()), + .constraint = getConstraint(cp), .constraint_width = constraintWidth(cell_pin), }, ); @@ -3045,27 +3044,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type { return; } - // We always use fg mode for sprite glyphs, since we know we never - // need to constrain them, and we don't have any color sprites. - // - // Otherwise we defer to `fgMode`. - const mode: shaderpkg.CellText.Mode = - if (render.glyph.sprite) - .fg - else switch (fgMode( - render.presentation, - cell_pin, - )) { - .normal => .fg, - .color => .fg_color, - .constrained => .fg_constrained, - .powerline => .fg_powerline, - }; - try self.cells.add(self.alloc, .text, .{ - .mode = mode, + .atlas = switch (render.presentation) { + .emoji => .color, + .text => .grayscale, + }, + .bools = .{ .no_min_contrast = noMinContrast(cp) }, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = cell.gridWidth(), .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3150,7 +3135,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; self.cells.setCursor(.{ - .mode = .cursor, + .atlas = .grayscale, + .bools = .{ .is_cursor_glyph = true }, .grid_pos = .{ x, screen.cursor.y }, .color = .{ cursor_color.r, cursor_color.g, cursor_color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, @@ -3199,7 +3185,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Add our text try self.cells.add(self.alloc, .text, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(coord.x), @intCast(coord.y) }, .color = .{ fg.r, fg.g, fg.b, 255 }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 9fe0862ed..bf3bcc6e4 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -269,15 +269,16 @@ pub const CellText = extern struct { 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, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u8) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; test { diff --git a/src/renderer/opengl/shaders.zig b/src/renderer/opengl/shaders.zig index 0b67eaff0..80980bac7 100644 --- a/src/renderer/opengl/shaders.zig +++ b/src/renderer/opengl/shaders.zig @@ -237,15 +237,16 @@ pub const CellText = extern struct { bearings: [2]i16 align(4) = .{ 0, 0 }, grid_pos: [2]u16 align(4), color: [4]u8 align(4), - mode: Mode align(4), - constraint_width: u32 align(4) = 0, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u32) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; // test { diff --git a/src/renderer/shaders/glsl/cell_text.f.glsl b/src/renderer/shaders/glsl/cell_text.f.glsl index fda6d8134..176efcbde 100644 --- a/src/renderer/shaders/glsl/cell_text.f.glsl +++ b/src/renderer/shaders/glsl/cell_text.f.glsl @@ -4,21 +4,15 @@ layout(binding = 0) uniform sampler2DRect atlas_grayscale; layout(binding = 1) uniform sampler2DRect atlas_color; in CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; } in_data; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -// -// NOTE: this must be kept in sync with the fragment shader -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; @@ -27,12 +21,9 @@ void main() { bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0; bool use_linear_correction = (bools & USE_LINEAR_CORRECTION) != 0; - switch (in_data.mode) { + switch (in_data.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: + case ATLAS_GRAYSCALE: { // Our input color is always linear. vec4 color = in_data.color; @@ -84,7 +75,7 @@ void main() { return; } - case MODE_TEXT_COLOR: + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. diff --git a/src/renderer/shaders/glsl/cell_text.v.glsl b/src/renderer/shaders/glsl/cell_text.v.glsl index 10965ddd2..7e38e2f0c 100644 --- a/src/renderer/shaders/glsl/cell_text.v.glsl +++ b/src/renderer/shaders/glsl/cell_text.v.glsl @@ -15,22 +15,22 @@ layout(location = 3) in uvec2 grid_pos; // The color of the rendered text glyph. layout(location = 4) in uvec4 color; -// The mode for this cell. -layout(location = 5) in uint mode; +// Which atlas this glyph is in. +layout(location = 5) in uint atlas; -// The width to constrain the glyph to, in cells, or 0 for no constraint. -layout(location = 6) in uint constraint_width; +// Misc glyph properties. +layout(location = 6) in uint glyph_bools; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; + +// Masks for the `glyph_bools` attribute +const uint NO_MIN_CONTRAST = 1u; +const uint IS_CURSOR_GLYPH = 2u; out CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; @@ -69,7 +69,7 @@ void main() { corner.x = float(vid == 1 || vid == 3); corner.y = float(vid == 2 || vid == 3); - out_data.mode = mode; + out_data.atlas = atlas; // === Grid Cell === // +X @@ -102,25 +102,6 @@ void main() { offset.y = cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (mode == MODE_TEXT_CONSTRAINED) { - float max_width = cell_size.x * constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2.0; - offset.x = 0.0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.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 + size * corner + offset; @@ -149,11 +130,7 @@ void main() { // 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. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // 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 (min_contrast > 1.0f && mode == MODE_TEXT) { + if (min_contrast > 1.0f && (glyph_bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out_data.color = contrasted_color(min_contrast, out_data.color, out_data.bg_color); } @@ -161,8 +138,9 @@ void main() { // Check if current position is under cursor (including wide cursor) bool is_cursor_pos = ((grid_pos.x == cursor_pos.x) || (cursor_wide && (grid_pos.x == (cursor_pos.x + 1)))) && (grid_pos.y == cursor_pos.y); - // If this cell is the cursor cell, then we need to change the color. - if (mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((glyph_bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out_data.color = load_color(unpack4u8(cursor_color_packed_4u8), use_linear_blending); } } diff --git a/src/renderer/shaders/shaders.metal b/src/renderer/shaders/shaders.metal index b62e0c3cf..4797f89e4 100644 --- a/src/renderer/shaders/shaders.metal +++ b/src/renderer/shaders/shaders.metal @@ -509,13 +509,17 @@ fragment float4 cell_bg_fragment( //------------------------------------------------------------------- #pragma mark - Cell Text Shader -// The possible modes that a cell fg entry can take. -enum CellTextMode : uint8_t { - MODE_TEXT = 1u, - MODE_TEXT_CONSTRAINED = 2u, - MODE_TEXT_COLOR = 3u, - MODE_TEXT_CURSOR = 4u, - MODE_TEXT_POWERLINE = 5u, +enum CellTextAtlas : uint8_t { + ATLAS_GRAYSCALE = 0u, + ATLAS_COLOR = 1u, +}; + +// We use a packed struct of bools for misc properties of the glyph. +enum CellTextBools : uint8_t { + // Don't apply min contrast to this glyph. + NO_MIN_CONTRAST = 1u, + // This is the cursor glyph. + IS_CURSOR_GLYPH = 2u, }; struct CellTextVertexIn { @@ -534,16 +538,16 @@ struct CellTextVertexIn { // The color of the rendered text glyph. uchar4 color [[attribute(4)]]; - // The mode for this cell. - uint8_t mode [[attribute(5)]]; + // Which atlas to sample for our glyph. + uint8_t atlas [[attribute(5)]]; - // The width to constrain the glyph to, in cells, or 0 for no constraint. - uint8_t constraint_width [[attribute(6)]]; + // Misc properties of the glyph. + uint8_t bools [[attribute(6)]]; }; struct CellTextVertexOut { float4 position [[position]]; - uint8_t mode [[flat]]; + uint8_t atlas [[flat]]; float4 color [[flat]]; float4 bg_color [[flat]]; float2 tex_coord; @@ -577,7 +581,7 @@ vertex CellTextVertexOut cell_text_vertex( corner.y = float(vid == 2 || vid == 3); CellTextVertexOut out; - out.mode = in.mode; + out.atlas = in.atlas; // === Grid Cell === // +X @@ -610,25 +614,6 @@ vertex CellTextVertexOut cell_text_vertex( offset.y = uniforms.cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (in.mode == MODE_TEXT_CONSTRAINED) { - float max_width = uniforms.cell_size.x * in.constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2; - offset.x = 0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.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 + size * corner + offset; @@ -665,11 +650,7 @@ vertex CellTextVertexOut cell_text_vertex( // 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. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // 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 && in.mode == MODE_TEXT) { + if (uniforms.min_contrast > 1.0f && (in.bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out.color = contrasted_color(uniforms.min_contrast, out.color, out.bg_color); } @@ -681,8 +662,9 @@ vertex CellTextVertexOut cell_text_vertex( in.grid_pos.x == uniforms.cursor_pos.x + 1 ) && in.grid_pos.y == uniforms.cursor_pos.y; - // If this cell is the cursor cell, then we need to change the color. - if (in.mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((in.bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out.color = load_color( uniforms.cursor_color, uniforms.use_display_p3, @@ -702,19 +684,12 @@ fragment float4 cell_text_fragment( constexpr sampler textureSampler( coord::pixel, address::clamp_to_edge, - // TODO(qwerasd): This can be changed back to filter::nearest when - // we move the constraint logic out of the GPU code - // which should once again guarantee pixel perfect - // sizing. - filter::linear + filter::nearest ); - switch (in.mode) { + switch (in.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: { + case ATLAS_GRAYSCALE: { // Our input color is always linear. float4 color = in.color; @@ -764,7 +739,7 @@ fragment float4 cell_text_fragment( return color; } - case MODE_TEXT_COLOR: { + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. float4 color = textureColor.sample(textureSampler, in.tex_coord);